Hi :
I am working on a custom board equipped with K66 MCU, three quadrature decoders and other peripherals. Since three quadrature decoders, I am going to utilized FTM1, and 2 for two devices, and general gpio interrupt for the other. The software development is based on KDS3.0 and KSDK1.2. The first thing I would like to do is verifying the integrity of GPIO interrupt from encoder. I basically jump the FTM1_QD_PHA and FTM1_QD_PHB signals to PTE8, and PTE9, and a motor encoder signal connects to FTM1. Therefore, the same encoder signal should feed to both FTM1 and GPIO port E. When I test it with bare board and SDK platform only, the had pretty match count from both decoder. This gives me confirmation that FTM and GPIO interrupt and decoder algorithm are running OK. However, if I have similar code and utilize SDK with MQX, I am facing lose count on GPIO interrupt. For example I ran the motor about 3 seconds (the encoder signals tick in at 6.64kHz, 150us period), the FTM reads 61438 count, but GPIO interrupt counts 60967. I am wondering if I miss something, or this is drawback of utilizing MQX. Does MQX take lots of CPU resource that cause losing interrupt? If you have any idea or recommendation please let me know. Thank you very much.
main.c without MQX
// Standard C Included Files #include <stdio.h> // SDK Included Files #include "board.h" #include "gpio_pins.h" #include "fsl_debug_console.h" #include "fsl_device_registers.h" #include "fsl_ftm_driver.h" #include "fsl_ftm_hal.h" //----------------------------------------------------------------------- // Function Prototypes //----------------------------------------------------------------------- void gpio_Init(void); void encoderX_Init(void); void encoderZ_Init(void); void FlexTimer1_IRQHandler(void); uint8_t ScanEcn(uint8_t InputAB, uint8_t PreviousStateAB, int64_t *EncCnt); void BOARD_ENCZ_IRQ_HANDLER(void); int64_t GetEncXCnt(void); //----------------------------------------------------------------------- // Constants //----------------------------------------------------------------------- #define ENCX_OVERFLOW 4000 //1000 CPR(cycle/rev) #define ENCY_OVERFLOW 4000 //1000 CPR(cycle/rev) //----------------------------------------------------------------------- // Typedefs //----------------------------------------------------------------------- //----------------------------------------------------------------------- // Global Variables //----------------------------------------------------------------------- int64_t EncXOF = 0; int64_t PosX = 0; int64_t PosZ = 0; int64_t PosXPV = 0; int64_t PosZPV = 0; volatile uint8_t stateOld = 0; //----------------------------------------------------------------------- // Main Function //----------------------------------------------------------------------- int main(void) { /* Write your code here */ // Init hardware hardware_init(); dbg_uart_init(); PRINTF("\r\n\nRunning QEncIntTest example."); gpio_Init(); encoderX_Init(); encoderZ_Init(); PosXPV = GetEncXCnt(); PosXPV = PosZPV = PosZ; /* This for loop should be replaced. By default this loop allows a single stepping. */ for (;;) { PosX = GetEncXCnt(); if (PosX != PosXPV || PosZ != PosZPV) { PRINTF("\r\nPosX = %ld, PosZ = %ld", (int32_t)PosX, (int32_t)PosZ); PosXPV = PosX; PosZPV = PosZ; } } /* Never leave main */ return 0; } void gpio_Init(void) { gpio_output_pin_user_config_t outputPinConfig; outputPinConfig.pinName = kGpioE0; outputPinConfig.config.outputLogic = 0; outputPinConfig.config.slewRate = kPortSlowSlewRate; outputPinConfig.config.isOpenDrainEnabled = false; outputPinConfig.config.driveStrength = kPortLowDriveStrength; GPIO_DRV_OutputPinInit(&outputPinConfig); } void encoderX_Init(void) { FTM_Type *ftmBase; // Configure FTM1,2 encoder pins ftmBase = g_ftmBase[BOARD_ENCX_FTM_INSTANCE]; configure_ftm_pins(BOARD_ENCX_FTM_INSTANCE); ftm_user_config_t flexTimer1_InitConfig0 = { .tofFrequency = 0U, .isWriteProtection = false, // Disable write protection .BDMMode = kFtmBdmMode_11, .syncMethod = kFtmUseSoftwareTrig }; FTM_DRV_Init(BOARD_ENCX_FTM_INSTANCE,&flexTimer1_InitConfig0); FTM_HAL_SetMod(ftmBase, ENCX_OVERFLOW-1); FTM_HAL_SetCounterInitVal(ftmBase, 0); FTM_DRV_SetClock(BOARD_ENCX_FTM_INSTANCE, kClock_source_FTM_SystemClk, kFtmDividedBy1); //start the timer clock, source is the the phase A and B input signals ftm_phase_params_t flexTimer1_QdConfig0 = { .kFtmPhaseInputFilter = false, .kFtmPhaseFilterVal = 0U, .kFtmPhasePolarity = kFtmQuadPhaseNormal, }; FTM_DRV_QuadDecodeStart(BOARD_ENCX_FTM_INSTANCE, &flexTimer1_QdConfig0, &flexTimer1_QdConfig0, kFtmQuadPhaseEncode); FTM_DRV_SetTimeOverflowIntCmd(BOARD_ENCX_FTM_INSTANCE,true); FTM_DRV_SetFaultIntCmd(BOARD_ENCX_FTM_INSTANCE,false); } void encoderZ_Init(void) { GPIO_DRV_Init(encoderZPins, NULL); //configure QD_PHB and QD_PHB signal } /* FTM1 IRQ handler that would cover the same name's APIs in startup code. */ extern void FTM_DRV_IRQHandler(uint32_t instance); void FTM1_IRQHandler(void) { if((FTM1_QDCTRL & FTM_QDCTRL_TOFDIR_MASK) == 0) EncXOF--; else EncXOF++; FTM_DRV_IRQHandler(1); } uint8_t ScanEcn(uint8_t InputAB, uint8_t PreviousStateAB, int64_t *EncCnt) { switch (PreviousStateAB){ case 0x0: if(InputAB == 0x1){ (*EncCnt)++; }else if(InputAB == 0x2){ (*EncCnt)--; } break; case 0x1: if(InputAB == 0x0){ (*EncCnt)--; }else if(InputAB == 0x3){ (*EncCnt)++; } break; case 0x2: if(InputAB == 0x0){ (*EncCnt)++; }else if(InputAB == 0x3){ (*EncCnt)--; } break; case 0x3: if(InputAB == 0x1){ (*EncCnt)--; }else if(InputAB == 0x2){ (*EncCnt)++; } break; default: break; } return InputAB; } /* PORTE IRQ handler that would cover the same name's APIs in startup code. */ void PORTE_IRQHandler(void) { uint8_t state; if (PORTE_PCR8 & PORT_PCR_ISF_MASK) { //interrupt on A changing state PORTE_PCR8 |= PORT_PCR_ISF_MASK; // clear the flag state = (uint8_t)((GPIOE_PDIR & 0x00000300) >> 8); stateOld = ScanEcn(state, stateOld, &PosZ); } if (PORTE_PCR9 & PORT_PCR_ISF_MASK) { //interrupt on B changing state PORTE_PCR9 |= PORT_PCR_ISF_MASK; // clear the flag state = (uint8_t)((GPIOE_PDIR & 0x00000300) >> 8); stateOld = ScanEcn(state, stateOld, &PosZ); } } int64_t GetEncXCnt(void) { return ((ENCX_OVERFLOW * EncXOF) + FTM1_CNT); }
main.c without MQX
//----------------------------------------------------------------------- // Standard C/C++ Includes //----------------------------------------------------------------------- #include <stdio.h> #include "main.h" void main_task(uint32_t param); void task_example(task_param_t param); void gpio_Init(void); void encoderX_Init(void); void encoderZ_Init(void); void FlexTimer1_IRQHandler(void); uint8_t ScanEcn(uint8_t InputAB, uint8_t PreviousStateAB, int64_t *EncCnt); void BOARD_ENCZ_IRQ_HANDLER(void); int64_t GetEncXCnt(void); //----------------------------------------------------------------------- // Constants //----------------------------------------------------------------------- #define MAIN_TASK 8U const TASK_TEMPLATE_STRUCT MQX_template_list[] = { { MAIN_TASK, main_task, 0xC00, 20, "main_task", MQX_AUTO_START_TASK}, { 0L, 0L, 0L, 0L, 0L, 0L } }; #define TASK_EXAMPLE_PRIO 6U #define TASK_EXAMPLE_STACK_SIZE 1024U #define ENCX_OVERFLOW 4000 //1000 CPR(cycle/rev) #define ENCY_OVERFLOW 4000 //1000 CPR(cycle/rev) //----------------------------------------------------------------------- // Global Variables //----------------------------------------------------------------------- int64_t EncXOF = 0; int64_t PosX = 0; int64_t PosZ = 0; int64_t PosXPV = 0; int64_t PosZPV = 0; volatile uint8_t stateOld = 0; //----------------------------------------------------------------------- // Macros //----------------------------------------------------------------------- OSA_TASK_DEFINE(task_example, TASK_EXAMPLE_STACK_SIZE); //----------------------------------------------------------------------- // Main Function //----------------------------------------------------------------------- void main_task(uint32_t param) { osa_status_t result = kStatus_OSA_Error; printf("\n\nRunning the KSDK_1_2_0_PCB58852 project."); gpio_Init(); encoderX_Init(); encoderZ_Init(); PosXPV = GetEncXCnt(); PosZ = PosXPV; PosZPV = PosXPV; // OSA_Init(); // result = OSA_TaskCreate(task_example, // (uint8_t *)"example", // TASK_EXAMPLE_STACK_SIZE, // task_example_stack, // TASK_EXAMPLE_PRIO, // (task_param_t)0, // false, // &task_example_task_handler); // if (result != kStatus_OSA_Success) // { // printf("Failed to create example task\r\n"); // return; // } // OSA_Start(); for (;;) // Forever loop { PosX = GetEncXCnt(); if (PosX != PosXPV || PosZ != PosZPV) { printf("\nPosX = %ld, PosZ = %ld", (int32_t)PosX, (int32_t)PosZ); PosXPV = PosX; PosZPV = PosZ; } OSA_TimeDelay(100); //500ms delay } } //----------------------------------------------------------------------- // Task Functions //----------------------------------------------------------------------- // //void task_example(task_param_t param) //{ // printf("\r\nHellow World.\r\n"); // // while(1) // { // __asm("NOP"); // } //}
Hi Peter,
Is your GPIO input interrupt configured for edge or level (i.e. low level or high level) trigger?
An edge interrupt will remain valid until the ISR is cleared. A level interrupt goes away if it reverts back to non-asserted level.
Are the FTM and GPIO interrupts the same or different priority level?
In summary:
Edge gpio is persistent.
Level gpio is non-persistent.
Regards,
David
Hi David:
The GPIO interrupt is edge interrupted. As you can see in the code, I clear the interrupt flag before reading the port for process. I didn't specified the interrupt priority for both. I will assume it stay the same as MQX specified in the default.
Since you mentioned about priority. I have tried declear GPIO interrupt priority NVIC_SetPriority(PORTE_IRQn, 2U); in the encoderZ_Init(). I see match reading in a very short time, after a while the software stopped. If I set priority to 4. it doesn't help! But the software continue run without problem. As my understanding, priority 2 is the highest priority in the MQX default. Let me know if any other though. Thanks!
Peter Shih
Hi Peter,
Did you resolve this? If yes, how?
If no, you can always insert your ISR directly into the vector table (called a gorilla interrupt) that will preempt the RTOS.
This is OK as long as you do not make RTOS calls. So use a global variable to keep track of count.
Regards,
David
Hi David:
Appreciate your feedback. I am sorry replying so late. The problem is still not resolved yet. Actually, I was working with Freescale support group regarding this issue. However, I haven't receive any positive feedback yet.
You mentioned a good point. It's related with interrupt. As the source code enclosed, I though I did replace the port E interrupt with a custom interrupt service routine to process the count (PosZ), and the count is declared as a global variable. I am not sure if I understand your recommendation clearly. Could you give me more detail though or any sample code? Thanks your feedback.
Hi Peter,
Sorry for my delay.
I have had another customer with similar issue not using MQX. When he dug into the cause it was disabling global interrupts for too long which caused the missing edges.
Regards,
David
Hi David:
Thanks your information. I don't have any global interrupt disabling in my code. Of cause, I don't have any idea if MQX does this.
Best Regards,
Peter Shih
Hello Peter,
" Due to the MQX RTOS interrupt handling mechanism, the names of the ISR entry functions must be different than those in the CMSIS startup code file (startup_<device>.S), otherwise the kernel ISR is not called when the corresponding interrupt triggers. "
These words is from the DOC of my colleague Jorge Gonzalez :
Interrupt handling with KSDK and Kinetis Design Studio
I think you can check this , maybe it can help you .
Hope it helps
-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------
Have a great day,
Alice