System:
- Keil MDK
- Apollo3 MCU
- Keil RTX
- Arm compiler 5.06 Update 6 (build 750)
Problem:Inside keil rtx function: os_idle_demon we suspend rtos and then enter deepsleep mode
But, it looks like calling rt_suspend inside os_idle_demon results in 90 uA current draw, preventing deepsleep mode current savings (we should only be seeing around 10uA current)
Function:
/// \brief The idle demon that runs when no other thread is ready to run void os_idle_demon (void) { for (;;) { _mcu_manage_idle(); } } static inline void _mcu_manage_idle(void) { extern const uint32_t k_stimer_ticks_per_rtos_tick; // defined in RTX_Conf_CM.c uint32_t rtos_ticks_to_sleep; uint32_t rt_suspend(void); // defined in RTX, avoid using os_suspend because it sets event register // We need to do this BASEPRI+WFE because // // a) If we call os_suspend(), and an interrupt that triggers thread work // occurs, there might be a chance that we mistakenly enter deep sleep // even though there is work to do. // // b) We can't call os_suspend() with IRQs disabled, because os_suspend() // makes an SVC call and will HardFault if IRQs are disabled. // // c) We can call rt_suspend() (which is what os_suspend() calls internally) // but we have to set BASEPRI to ensure that SysTick/PendSV does not // inadvertently run and corrupt RTOS state while rt_suspend() modifies it. // // d) This leaves us with the issue of an IRQ being triggered while this is // all occurring. To solve this, we use the WFE instruction instead of the // WFI instruction. This will ensure that if an IRQ is triggered, the next // WFE instruction will simply clear the event register without putting // the CPU to sleep, because any IRQ sets the event register when it is // triggered. __set_BASEPRI(0xFF); // prevent PendSV from being handled rtos_ticks_to_sleep = rt_suspend(); __set_BASEPRI(0); // _enable_irq() ? if (0 == rtos_ticks_to_sleep) { goto do_resume; } { // bracket avoids "bypassed initialization" warnings from goto do_resume // Configure STIMER B/1 to wake us up // Don't use STIMER A/0 because RTX is already using that IRQ handler const uint32_t stimer_sleep_start = am_hal_stimer_counter_get(); const uint32_t stimer_sleep_max = 0x80000000u / k_stimer_ticks_per_rtos_tick * k_stimer_ticks_per_rtos_tick; // round down to whole RTOS tick const uint64_t stimer_sleep_ticks64 = (uint64_t)k_stimer_ticks_per_rtos_tick * (uint64_t)rtos_ticks_to_sleep; const uint32_t stimer_sleep_ticks = stimer_sleep_ticks64 < stimer_sleep_max ? stimer_sleep_ticks64 : stimer_sleep_max; am_hal_stimer_int_enable(AM_HAL_STIMER_INT_COMPAREB); am_hal_stimer_int_clear(AM_HAL_STIMER_INT_COMPAREB); am_hal_stimer_compare_delta_set(1, stimer_sleep_ticks); NVIC_SetPriority(STIMER_CMPR1_IRQn, 0xFF); NVIC_EnableIRQ(STIMER_CMPR1_IRQn); /////////////////// ENTER SLEEP disable_some_peripherals(); // i2c, spi ..etc SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; __WFE(); enable_some_peripherals(); /////////////////// EXIT SLEEP // Figure out how long we stopped. const uint32_t stimer_sleep_end = am_hal_stimer_counter_get(); rtos_ticks_to_sleep = ((stimer_sleep_end - stimer_sleep_start) + (k_stimer_ticks_per_rtos_tick / 2)) / k_stimer_ticks_per_rtos_tick; // Disable STIMER B/1 now that we're done with it. NVIC_DisableIRQ(STIMER_CMPR1_IRQn); am_hal_stimer_int_disable(AM_HAL_STIMER_INT_COMPAREB); } // avoid warnings from goto do_resume do_resume: os_resume(rtos_ticks_to_sleep); // start scheduler back up and inform how many ticks surpassed. __SEV(); __WFE(); // clear CPU event flag }
Why is rt_suspend causing such (relatively) high current draw and how can we fix this?