Skip to content

Full sleep() documentation? #10192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
hierophect opened this issue Mar 21, 2019 · 16 comments
Closed

Full sleep() documentation? #10192

hierophect opened this issue Mar 21, 2019 · 16 comments

Comments

@hierophect
Copy link

hierophect commented Mar 21, 2019

Description

Where is the sleep() function fully documented? The power management section of the API only mentions it in passing, instead focusing on how to disable it/re-enable it, and how it is called when all threads are idle. I find that calling the sleep() function has no effect whatsoever on my ST boards (F103, F411, F401), though in most mbed classic examples it is referenced as though it will immediately halt the processor.

I find it also worth noting that the current example on the power management page does not work out of the box, only Thread::wait() sends the processor into sleep mode. Issue was actually MBED_TICKLESS

Issue request type

[x] Question
[ ] Enhancement
[ ] Bug
@ciarmcom
Copy link
Member

Internal Jira reference: https://jira.arm.com/browse/MBOCUSTRIA-1042

@0xc0170
Copy link
Contributor

0xc0170 commented Mar 22, 2019

Can you share some references - is it doxygen docs, our docs. Looking at the API level, sleep and power mng headers have docs, would like to know which ones there lack details.

What do you want to enable or disable it?

This should be captured in the documentation:

mbed classic - explicit call to sleep
Mbed OS - idle loop invokes sleep

https://github.com/ARMmbed/mbed-os-5-docs/blob/e0703742672f72e746c1f08da7828750091200b1/docs/api/platform/PowerManagement.md
https://github.com/ARMmbed/mbed-os-5-docs/blob/e0703742672f72e746c1f08da7828750091200b1/docs/api/platform/IdleLoop.md

@kjbracey
Copy link
Contributor

I agree there's something documentation issue here. But sleep() has been pushed down because it shouldn't be called by applications - it's something the RTOS idle does for you once all the threads are sleeping. (And calling it isn't totally trivial, to be race-free).

Also note that calling sleep() on its own only sleeps until the next interrupt. That could be a tiny fraction of a second. It's not a "suspend", it's really just an "idle" or "wait for interrupt".

Really sleep() should be just for internal use only, but in bare metal we don't yet have those helpers. My PR #10104 provides the infrastructure to take control of the sleep() for bare metal builds too, by putting it inside ThisThread::sleep_for() or Semaphore::acquire() again.

Also, we do need a system suspend call which really would shut off until a limited specific wakeup, not any arbitrary interrupt or timer - #10104 is a step towards that.

@kjbracey
Copy link
Contributor

Looking at the docs, I guess what's missing is the concrete description of what sleep() is.

My quick summary:

  • sleep() calls either hal_sleep() or hal_deepsleep() depending on whether deep sleep is locked/permitted.
  • Those HAL functions differ on required wake up sources and latency. This is documented in sleep_api.h
  • Both sleep functions are expected to work as ARM WFI - they are normally called from a critical section, and return when there is a pending interrupt.
  • Most race-free uses of sleep() require that it be called from a critical section. Only sleep() in an infinite loop would reasonably work without a critical section.
  • A valid minimal implementation of hal_sleep or deepsleep is an empty function.
  • A minimal reasonable implementation would be a single WFI instruction.
  • More usually hal_sleep is just WFI, just stopping the core clock, and hal_deepsleep is a WFI surrounded by a power control setting on the device that makes the WFI go deeper than just "idle" - limiting the wakeup sources and functioning peripherals.

Also, the example on the power management page looks fine to me, at least for an RTOS build - wait(1.0) defers to ThisThread::sleep_for().

void wait(float s)
{
    if ((s >= 0.01f)  && core_util_are_interrupts_enabled()) {
        rtos::ThisThread::sleep_for(s * 1000.0f);
        return;
    }
}

So I don't see why Thread::wait() would do anything different. The example wouldn't work bare metal, but that's what #10104 is for.

@hierophect
Copy link
Author

hierophect commented Mar 22, 2019

I should note that I have been running into this issue in attempting to migrate a number of power-sensitive applications from mbed 2 to mbed 5, since 2 is effectively no longer supported, and thus I'm hoping to find documentation that eases that process. I understand that sleep() is not really supposed to be called in the context of an RTOS operation; I believe could achieve the same task with an event queue/thread and Thread::wait(osWaitForever).

But it does seem like overkill for an application to implement an event queue when it only has a single 2-10 minute polling operation that it should only perform once, when an interrupt occurs, and sleep for the remaining 24 hours of the day. This is a common theme for almost all my mbed classic projects, and I think environmental sensors in general.

For instance, here is the mbed classic code that I've found recommended for this task:

#include "mbed.h"
#include "PinDetect.h"

DigitalOut led(PA_3);
PinDetect btn(PA_1);

void isr_buttonpress() {
    
}

int main() {
    btn.attach_deasserted(&isr_buttonpress);
    btn.setSampleFrequency();
    while(1) {
        sleep();
        for(int i=0;i<5;i++) {  
            led = 1; // LED is ON
            wait(0.5); // 200 ms
            led = 0; // LED is OFF
            wait(0.5); // 1 sec
        }
    }
}

If this code is no longer expected to work (due to consistent os-level interrupts that don't occur in classic for instance), I would appreciate being able find out why via the documentation, and the recommended way to achieve the same structure without implementing a minimum of unnecessary queue overhead. I think this is a common enough task among IoT sensors that this would be worth explaining.

I suppose that "is it appropriate to use mbed-os for simple applications without any of the RTOS tools because it has the only public collection of device libraries and basic filesystem APIs besides Arduino" is a different question, but that's not one I've yet been able to get out of your corporate guys :)

@hierophect
Copy link
Author

also, @kjbracey-arm, is the bare-metal parameter documented anywhere?

@kjbracey
Copy link
Contributor

kjbracey commented Mar 22, 2019

That code is assuming that there's only 1 interrupt in the entire system, and it's your button. And it's also assuming sleep() never spuriously wakes or is turned into a no-op by a debugger being attached.

On the one hand, yes, we obviously don't want there to be lots of other wake-up sources in the system stopping you getting to sleep, but your application needs to be tolerant in case that happens, in any environment. You can't assume that your button has been pressed just because sleep() returns. You need to actually check it.

If wanting to support current bare-metal in 5.12, or mbed OS 2, I would recommend

bool button_pressed;

void isr_buttonpress() {
    button_pressed = true;
}

// in main, in place of `sleep()`
core_util_enter_critical_section();
while (!button_pressed) { // critical section avoids race between check and sleep
   sleep();
   core_util_exit_critical_section();
   __ISB(); // give interrupt handler a chance to run and set button_pressed
   core_util_enter_critical_section();
}
button_pressed = false;
core_util_exit_critical_section();

That will be tolerant to any other interrupts happening, or any other spurious wakeups. It's obviously clunky, but I think that's the best you can currently do. (General rule of thumb for low-level sleep primitives like sleep(), WFI and WFE - the code must function correctly if they're implemented as a no-op).

If the RTOS is there mbed OS 5, you just use:

EventFlags events;
void isr_buttonpress() {
    events.set(1);
}

// in main, in place of `sleep()`
events.wait_all(1);

#10104 is intending to make APIs like EventFlags::wait work in bare metal builds too, which would get rid of (hide) all the clunkiness above, and give a nice level of source compatibility

Your sleep() doesn't work in the RTOS because while the RTOS is running there's a 1ms ticker used to monitor running threads. If you just call sleep(), that ticker keeps going, and you will be woken up by it in 1ms. (So the OS can consider taking CPU time away from your thread because your thread is hogging the system - by calling sleep(), your thread is consuming 100% of notional CPU time, not letting other threads run!)

You have to actually get the OS involved and let it decide to stop the ticker - you do that by actually sleeping your thread with an OS sleeping call. In this case, EventFlags::wait_all. That makes your thread block, the OS is idle, and the idle thread runs, stops the ticker (if MBED_TICKLESS is set, which for ST boards it is), and then calls sleep().

I'm currently looking at a suspend() API which would do a system-level "brute-force" suspend, not caring about any other threads that are running, and limiting wake-up sources, but that's a work in progress. For now, you need to achieve sleep by making all threads sleep.

The bare metal profile is new to mbed OS 5.12, which isn't quite finalised yet, so not sure if the documentation is up. PR was #9800.

@hierophect
Copy link
Author

hierophect commented Mar 22, 2019

Thank you for the detailed reply, lots of good information. Brute force suspend would be very useful for simple applications and I hope it makes it in. Do you think your rtos example would be worth adding to the power management section, so that there's a reference example for interrupt driven wake from sleep code, as opposed to polling only?

I slipped up with my issue with wait() - the issue was actually MBED_TICKLESS, which I must have been messing with at the same time as Thread::wait() and gotten it confused. I should note that the mbed online compiler DOES NOT include MBED_TICKLESS by default, and it appears to be required for any power savings whatsoever on the ST boards. Tickless is only documented in the Porting Targets section of the documentation and is not linked in the power management section - I think that is absolutely worth linking since it seems to be essential to actually see any current savings (5.2mA constant draw on Power Management example without it, and 450uA/4.8mA with it.)

It seems this issue may also be very sensitive to board subfamily changes - sleep power savings, tickless or not, do not appear to activate at all on a STM32103C8T6 using the 103RB as a target. Probably no reason to document this since obviously boards like the bluepill aren't officially supported but it's the first time I have ever run into an incompatibility. I'm still confirming this against the 103RB which I won't have on hand until monday.

I'm extremely interested in the bare metal profile and would love to write an article on it when it's done. Is that intended as an up to date replacement of sorts for mbed 2? It'll take the flash and complexity back down to ultra-cheap Arduino levels which is very exciting from a rapid-prototyping perspective.

@hierophect
Copy link
Author

Also, I only found the Doxygen pages by literally stumbling on it in Google, might be nice to add a link there in the readme.

@kjbracey
Copy link
Contributor

On MBED_TICKLESS, for command-line builds that is currently set in targets.json for targets it's known to work on.

I see the 103RB lacks LPTICKER according to targets.json, and that omission would formerly have blocked MBED_TICKLESS, which is probably why it's not defined. But at the minute, STM boards have tickless-from-us-ticker true, which means it would now work, and in future TICKLESS will no longer have any LPTICKER dependency anyway. So maybe you could submit a patch to add it.

The bare-metal profile is intended to let you make Mbed 2-like images with Mbed OS 5, yes. I'm not directly involved in the tools/documentation work, but I'd expect to see more information + publicity on it soon.

@janjongboom
Copy link
Contributor

Note that we're reworking the sleep docs, and I've incorporated the info from this thread there. Should be up in the coming weeks.

@hierophect
Copy link
Author

@kjbracey-arm, does the potentially upcoming suspend() api include any support for shutting down peripherals like PWM, as referenced here?
#3945
The prospect of constantly freeing and creating PWM objects to allow the processor to sleep is just asking for memory fragmentation, and I've needed to bitbang the operation of my piezo speakers just to avoid the PWMOut API.

@kjbracey
Copy link
Contributor

kjbracey commented Apr 3, 2019

Current assumption is that suspend() will ignore the deep sleep lock - if you're explicitly asking for suspend you accept that peripherals will be suspended in general. So a PWMOut existing won't stop suspend using the deep sleep, where it would stop idle using deep sleep. (Because idle is supposed to be transparent).

@hierophect
Copy link
Author

How will that actually affect the peripherals in practice? PWMOut does not have an "off switch" so to speak other than just setting the pulsewidth to 0 - in Mbed 2, once you set the period, that's it, no more sleeping unless you free the instance since it'll constantly wake the processor. It isn't clear from the HAL or datasheet what happens when you just force suspend. Would we expect the PWM to just shut down? Or would it risk constantly re-waking the way it does in Mbed 2?

@kjbracey
Copy link
Contributor

kjbracey commented Apr 3, 2019

Suspend would be intended to be a system suspend, so the intent would be that you are basically off. The PWM output would be expected to stop.

Is this a software PWM in the system you're talking about? If it's software, then it would naturally stop. We'd have masked off whatever timer interrupt was timing it. If it is hardware, it would probably stop because we'd stopped whichever clock by entering deep sleep. There may be certain systems where it would keep going unless we took extra action.

(Basic suspend functionality is to mask all interrupts except designated wakeup sources.)

@linlingao
Copy link
Contributor

Per Jan's comment, doc will be updated. Please reopen if you still have issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants