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
  • 0
    25337 pts.
    in reply to Ole Bauck

    Hmm that's confusing. If the interrupt is disabled then it shouldn't wake the processor from WFI, nor WFE, unless SEVONPEND is also set in which case even if disabled it should trigger WFE to wake, but only once, not continuously. Don't see why it continues to wake the processor.

  • So is the SD not entering the sleep state when it "sees" the event condition? This seems like another posting that RK had commented on, where the source of the sleep-exit-trigger was not apparent

    wakeup-immediately-from-sd_app_evt_wait-with-external-lfclk

  • 0
    25337 pts.
    in reply to Ole Bauck

    or I could just be wrong in my understanding of how disabled interrupts wake up the CPU. I thought they basically didn't, and if they did it was only once, only in WFE and only in certain circumstances. But I've been wrong before and usually end up learning something.

  • Learning is good.

    Learning now is understanding the link between WFE on events, and how pending levels in the NVIC relate to events that cause wakeup. I clearly see the condition occur, the FPU flag is set, and the exception condition in the NVIC is raised (NVIC_IPR1, bit 6) and never cleared. When I run the code without clearing, the sd_app_evt_wait() call never stalls, returns immediately. As Darren said, if I comment out the math instruction, the NVIC pending is not set, and then the instrumented idle wait performs a very nice 1ms sleep wait, as I expect.

    When I leave the math instruction, and add the code that Ole suggested, I have partial success. The sd_nvic_ClearPendingIRQ() works, and I get expected results. If I leave the __set_FPSCR() and __DMB(); __DSB(); _ISB(); calls in, then ANY subsequent sd function calls hardfault.

    Still some testing to finish.

  • 0
    25337 pts.
    in reply to Ole Bauck

    And what if you don't use sd_app_evt_wait() but just __wfe(), since you're testing it it would be handy to know if the pending, disabled interrupt does keep the processor from sleeping at all, ie WFE continues to return instantly, in which case I get to read the manual again to work out why, or it's something that sd_app_evt_wait() does. I stopped using the latter a while ago because I never quite got comfortable that I knew exactly what it did.

Related