FPU divide by 0 and High Current Consumption

I was trying to track down high, approx. 6mA, processor current consumption and I have found high current consumption if the FPU executes a divide by 0. If I comment out the line the current consumption goes down to tens of micro amps. If the line is executed then the current is high since the processor is constantly coming out of sleep.

How does the the SDK deal with a divide by 0 with the FPU? I thought this was a hard fault?

Thanks

Parents
  • EDIT 2016-03-31: To be more inline with the release notes of the SDK the example code has been changed slightly. The two solutions are also presented equally, although it seems that only the interrupt solution works with FreeRTOS.

    EDIT 2016-03-11: Added explanation of why sd_app_evt_wait() does not allow the chip to sleep if pending FPU interrupt is not cleared. Solution with FPU interrupt implementation added as best solution. Previous solution kept as reference.

    The FPU will generate an exception when dividing by 0 due to overflow/underflow. This exception will trigger the FPU interrupt, see here. If the interrupt is not cleared/handled the CPU will not be able to go to sleep.

    The CPU will not be able to go to sleep if you are using sd_app_evt_wait(). It will return if there are any pending interrupts, even if the interrupts are not enabled through NVIC_EnableIRQ(..). You could use __WFE() instead, but then the application will wake up a lot more from events that are only handled by the SoftDevice. With SoftDevice S132 v2.0.0 sd_app_evt_wait() will also turn off MWU before calling WFE(), so using __WFE() from the application instead will lead to an increase in sleep power consumption of about 800uA.

    One way to fix this is to implement the FPU_IRQHandler. First make sure to have the latest Device Family Pack (8.5.0 at the time) such that the IRQ is included in the vector table. Set priority and enable IRQ in main:

    NVIC_SetPriority(FPU_IRQn, APP_IRQ_PRIORITY_LOW);
    NVIC_EnableIRQ(FPU_IRQn);
    

    Implement the FPU_IRQHandler and clear the FPSCR. The FPSCR state is pushed on the stack when we enter the interrupt and popped when we exit the the interrupt. Therefore we cannot use __set_FPSCR() since the value will be overwritten when we exit the interrupt. We have to change the stack value instead:

    #define FPU_EXCEPTION_MASK 0x0000009F
    
    void FPU_IRQHandler(void)
    {
        uint32_t *fpscr = (uint32_t *)(FPU->FPCAR+0x40);
        (void)__get_FPSCR();
    
        *fpscr = *fpscr & ~(FPU_EXCEPTION_MASK);
    }
    

    The other solution is to clear the interrupt every time the CPU wakes up from sleep. For example change the power_manage() function to:

    #define FPU_EXCEPTION_MASK 0x0000009F
    
    static void power_manage(void)
    {
        __set_FPSCR(__get_FPSCR()  & ~(FPU_EXCEPTION_MASK));      
        (void) __get_FPSCR();
        NVIC_ClearPendingIRQ(FPU_IRQn);
        
        uint32_t err_code = sd_app_evt_wait();
        APP_ERROR_CHECK(err_code);
    }
    
Reply
  • EDIT 2016-03-31: To be more inline with the release notes of the SDK the example code has been changed slightly. The two solutions are also presented equally, although it seems that only the interrupt solution works with FreeRTOS.

    EDIT 2016-03-11: Added explanation of why sd_app_evt_wait() does not allow the chip to sleep if pending FPU interrupt is not cleared. Solution with FPU interrupt implementation added as best solution. Previous solution kept as reference.

    The FPU will generate an exception when dividing by 0 due to overflow/underflow. This exception will trigger the FPU interrupt, see here. If the interrupt is not cleared/handled the CPU will not be able to go to sleep.

    The CPU will not be able to go to sleep if you are using sd_app_evt_wait(). It will return if there are any pending interrupts, even if the interrupts are not enabled through NVIC_EnableIRQ(..). You could use __WFE() instead, but then the application will wake up a lot more from events that are only handled by the SoftDevice. With SoftDevice S132 v2.0.0 sd_app_evt_wait() will also turn off MWU before calling WFE(), so using __WFE() from the application instead will lead to an increase in sleep power consumption of about 800uA.

    One way to fix this is to implement the FPU_IRQHandler. First make sure to have the latest Device Family Pack (8.5.0 at the time) such that the IRQ is included in the vector table. Set priority and enable IRQ in main:

    NVIC_SetPriority(FPU_IRQn, APP_IRQ_PRIORITY_LOW);
    NVIC_EnableIRQ(FPU_IRQn);
    

    Implement the FPU_IRQHandler and clear the FPSCR. The FPSCR state is pushed on the stack when we enter the interrupt and popped when we exit the the interrupt. Therefore we cannot use __set_FPSCR() since the value will be overwritten when we exit the interrupt. We have to change the stack value instead:

    #define FPU_EXCEPTION_MASK 0x0000009F
    
    void FPU_IRQHandler(void)
    {
        uint32_t *fpscr = (uint32_t *)(FPU->FPCAR+0x40);
        (void)__get_FPSCR();
    
        *fpscr = *fpscr & ~(FPU_EXCEPTION_MASK);
    }
    

    The other solution is to clear the interrupt every time the CPU wakes up from sleep. For example change the power_manage() function to:

    #define FPU_EXCEPTION_MASK 0x0000009F
    
    static void power_manage(void)
    {
        __set_FPSCR(__get_FPSCR()  & ~(FPU_EXCEPTION_MASK));      
        (void) __get_FPSCR();
        NVIC_ClearPendingIRQ(FPU_IRQn);
        
        uint32_t err_code = sd_app_evt_wait();
        APP_ERROR_CHECK(err_code);
    }
    
Children
Related