How to sleep / wake on interrupt?

02 Mar 2018

I'm trying to make the micro sleep when not in use, and wake to process any UART or SPI data received, or InterruptIn. How do I do this?

Currently in main(), I have the code below (just for the UART, for starters). When I run it, the micro doesn't ever wake. What am I doing wrong or missing?

I'm using an Embedded Artists LPC4088QSB, if it matters.

int main()
{
   DigitalOut led1(LED1, 1); // LED off
   Serial serial(USBTX, USBRX, 115200);

   // enable UART data rcvd interrupt to wake the micro, but just to get us back to
   //  the main loop, so no callback
   serial.attach(NULL, SerialBase::RxIrq);

   while (true)
   {
      __disable_irq();

      // interrupts disabled - atomically check for data received, and sleep if not
      if (serial.readable()) // only do the blocking getchar() call if data is available
      {
         __enable_irq(); // reenable interrupts ASAP, b/c we're staying awake
         led1 = !led1; // toggle LED
         char c = getchar();

         putchar(c); // echo char to stdout
         fflush(stdout); // ...immediately
      }
      else
      {
         sleep();
      }
   }
}
06 Mar 2018

You want to wake up with an Interrupt and you disabled the interrupts.

I am just guessing, I didn't test your code, but calling

__disable_irq();

and go to sleep isn't a good idea at all.

Btw why are you disabling and enabling IRQ's anyway?

06 Mar 2018

Phillipp Steiner wrote:

You want to wake up with an Interrupt and you disabled the interrupts.

Yes, because there needs to be a way to make an atomic operation which tests a condition and then conditionally sleeps based on the result.

Phillipp Steiner wrote:

I am just guessing, I didn't test your code, but calling

__disable_irq();

and go to sleep isn't a good idea at all.

Btw why are you disabling and enabling IRQ's anyway?

Consider this scenario:

  • if (serial.readable()) evaluates to false
  • interrupt occurs
  • sleep() is called

The micro wouldn't wake until the interrupt happens again.

Clearly I'm not implementing this correctly, but what I'm trying to do is a common pattern in embedded land.

I see that sleep() calls __WFI(). I thought __WFI() would atomically re-enable the interrupts and sleep - or else just not sleep if an interrupt is pending.

What am I doing wrong?

07 Mar 2018

No WFI() does not reenable IRQs by itself and why should it.

You dont need to call :

__disable_irq();

at all. If you want to have it "atomic" better use:

core_util_critical_section_enter();
[...]
core_util_critical_section_exit();

but "serial.readable()" is "atomic" itself because "Serial" inherits from "SerialBase" which reads the register and register operations are atomic.

Brendan McDonnell wrote:

What am I doing wrong?

As I already said going to sleep without enabling interrupts, you can wake up only with interrupts, except you are going to sleep with "WFE" but thats a different story.

07 Mar 2018

Phillipp Steiner wrote:

No WFI() does not reenable IRQs by itself and why should it.

As I said (and explained why above), there needs to be a way to make an atomic operation which {tests a condition and then conditionally sleeps based on the result}. Alternatively, it can be an atomic operation which {re-enables interrupts and then sleeps}. Apparently __WFI() by itself doesn't do that. I'm asking how to do it.

Phillipp Steiner wrote:

You dont need to call :

__disable_irq();

at all. If you want to have it "atomic" better use:

core_util_critical_section_enter();
[...]
core_util_critical_section_exit();

My revised code looks like this. Of course it still doesn't work because since core_util_critical_section_enter() calls __disable_irq(), but I still don't know what the solution is.

int main()
{
   DigitalOut led1(LED1, 1); // LED off
   Serial serial(USBTX, USBRX, 115200);
 
   // enable UART data rcvd interrupt to wake the micro, but just to get us back to
   //  the main loop, so no callback
   serial.attach(NULL, SerialBase::RxIrq);
 
   while (true)
   {
      core_util_critical_section_enter();
 
      // interrupts disabled - atomically check for data received, and sleep if not
      if (serial.readable()) // only do the blocking getchar() call if data is available
      {
         core_util_critical_section_exit(); // reenable interrupts ASAP, b/c we're staying awake
         led1 = !led1; // toggle LED
         char c = getchar();
 
         putchar(c); // echo char to stdout
         fflush(stdout); // ...immediately
      }
      else
      {
         hal_sleep();
         core_util_critical_section_exit();
      }
   }
}

Phillipp Steiner wrote:

but "serial.readable()" is "atomic" itself because "Serial" inherits from "SerialBase" which reads the register and register operations are atomic.

I don't need just the serial.readable() call to be atomic. It must be atomic together with the instructions to re-enable interrupts and go to sleep. (Or, atomic together with a conditional sleep instruction.)

Phillipp Steiner wrote:

Brendan McDonnell wrote:

What am I doing wrong?

As I already said going to sleep without enabling interrupts, you can wake up only with interrupts, except you are going to sleep with "WFE" but thats a different story.

I'm not calling __WFE. At least for TARGET_LPC408X, hal_sleep() calls __WFI().

07 Mar 2018

I know that you are not calling WFE...

serial.readable is an atomic operation because it reads the register, as I wrote...

Brendan Cassidy wrote:

but I still don't know what the solution is.

Enable Interrupts before going to sleep is the solution, otherwise you will never wake up!!!

And just as a tip, first try to wake up with triggering a pin, to get a feeling for sleep modes. Then the next step should be waking up from the USART. Start simple!

07 Mar 2018

(BTW, wrong username in your latest quote of me.)

Phillipp Steiner wrote:

Enable Interrupts before going to sleep is the solution, otherwise you will never wake up!!!

How do you address this scenario in the context of your suggestion?

  • if (serial.readable()) evaluates to false
  • interrupt occurs and is handled
  • sleep() is called

The micro wouldn't wake until the interrupt happens again.

08 Mar 2018

The WFI instruction doesn't put the core to sleep if its wake condition is true when the instruction is executed. For example if there is an uncleared IRQ when WFI is executed it acts as a NOP.

If you want to have only specific interrupts you want to wake up from, you have to mask the interrupts.

08 Mar 2018

If you're using mbed OS 5, you should be using RTOS mechanisms here.

By attempting to WFI/WFE in a thread, you are effectively hogging the CPU - using 100% of CPU time to sleep in this thread. Okay, you're hogging 100% of CPU time efficiently, but you're still hogging.

You should instead be using an RTOS call that waits for an event, to yield to other threads. Your interrupt handler could set an event flag or wait for a semaphore, and your thread could be waiting for that. Then the RTOS does all the sleeping stuff for you - but only when there are no other threads active either. The "idle thread" in the RTOS activates more HW-specific power-saving stuff, and will do better than any plain WFE/WFI you can do.

Aside from all that, on this discussion - WFE is a good general "wait for something to happen". It's designed to be safe to use from user applications, and may be a NOP. If you know you are waiting for a volatile flag or spinlock or something, then it's an appropriate mechanism. It will be woken automatically by any interrupt handler that runs on this CPU, and in an SMP system, you'd have to make to have SEV instructions to wake other CPUs. You do not disable interrupts when using it - there's no race because it's waiting for a sticky flag.

For your example WFE would work by:

  • local event flag is clear
  • if (serial.readable()) evaluates to false
  • interrupt occurs and is handled - this sets the local event flag
  • WFE is called - returns immediately because the local event flag is set.

WFI is for low-level "stop the CPU and other hardware". It's designed for OS use, and is a privileged instruction. Ths OS must disable interrupts before issuing it to avoid races - it will wake despite the interrupts being disabled, including if the interrupt is already pending. (If interrupts were enabled, there could be no pending interrupts, so there would be a race).

For your example WFI would work by:

  • disable interrupts
  • if (serial.readable()) evaluates to false
  • interrupt occurs but is not handled due to masking
  • WFI is called - returns immediately because the interrupt is pending
  • enable interrupts
  • interrupt is handled

But regardless, you should be using the RTOS

  • wait for event flag to be set - if it isn't already, so RTOS sleeps your thread
  • interrupt occurs and is handled - signals event flag
  • RTOS wakes your thread

The RTOS provides a few primitives that are safe(ish) to signal from IRQ context - semaphore, event flag and some others. Although there are limitations: https://github.com/ARM-software/CMSIS_5/issues/283

In terms of waiting for conditions, you may also be interested in ConditionVariable, present since mbed OS 5.7, I think: https://github.com/ARMmbed/mbed-os/blob/master/rtos/ConditionVariable.h But that can't be signalled from interrupt.

I've been messing with my own "ConditionVariableCS" which could be signalled from interrupt, but it's somewhat contentious, and maybe not the ideal answer: https://github.com/kjbracey-arm/mbed-os/commits/cv_cs

08 Mar 2018

BTW, for your specific example, I believe it doesn't work simply because SerialBase::attach(NULL) disables the interrupt. You'd need to give it a real routine, even if that routine is empty.

All of this complexity shouldn't be necessary in your simple example - a good console implementation would sleep properly while blocking getchar(). The default direct HAL serial is not such an implementation. But setting platform.stdio-buffered-serial to true makes it use UARTSerial, which does a better job. (Not perfect yet - it's still timed polling - but it can be improved. That cv_cs branch is such an attempt).

But if your fuller example has multiple wake-up sources, then this complexity has a purpose. RTOS event or thread flags are a good mechanism - each interrupt can have its own flag. See for example rf_if_irq_task here: https://github.com/ARMmbed/atmel-rf-driver/blob/master/source/NanostackRfPhyAtmel.cpp

02 May 2018

Kevin Bracey wrote:

If you're using mbed OS 5,

I am.

Kevin Bracey wrote:

you should be using RTOS mechanisms here.

Now I am. Thanks.

I do getchar() in my main thread.

I use mbed::InterruptIn::rise() with mbed::Callbacks, and an rtos::Semaphore to signal.

And the SPI stuff has gone on the back burner in my project for the moment, but I made an enhanced interrupt-based SPI Slave class, and I made a separate mbed::Thread / task for it. I'm planning to contribute that soon.

Please log in to post a reply.