This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

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);
    }
    
  • You made me actually test it! It appears to be an effect of sd_app_evt_wait(). It's quite hard to get it to do it all just using __wfe() directly if the FPU exception is disabled (which it is). You need SEVONPEND set to make the exception raise cause an event, else the pending exception is ignored, then you need to arrange for the exception to re-trigger in order to cause another event, just ignoring it entirely and leaving it pending doesn't raise another event and __wfe() doesn't return again. So for instance clearing the exception but leaving the FPU flags will re-raise it and then you'll get another event, but that's what it takes.

    Replacing __wfe() with sd_app_event_wait() makes the code busy loop constantly if the FPU exception is pending, even with SEVONPEND cleared and even if it were only raised the one time. sd_app_evt_wait() is clearly more conservative than __wfe().

Reply
  • You made me actually test it! It appears to be an effect of sd_app_evt_wait(). It's quite hard to get it to do it all just using __wfe() directly if the FPU exception is disabled (which it is). You need SEVONPEND set to make the exception raise cause an event, else the pending exception is ignored, then you need to arrange for the exception to re-trigger in order to cause another event, just ignoring it entirely and leaving it pending doesn't raise another event and __wfe() doesn't return again. So for instance clearing the exception but leaving the FPU flags will re-raise it and then you'll get another event, but that's what it takes.

    Replacing __wfe() with sd_app_event_wait() makes the code busy loop constantly if the FPU exception is pending, even with SEVONPEND cleared and even if it were only raised the one time. sd_app_evt_wait() is clearly more conservative than __wfe().

Children
No Data
Related