M0 wait for interrupt

I have working sketch that does what I want.  However, I'm curious as to why certain things function the way they do.

The application is a compass using the Adafruit Feather M0 Basic Proto, Feather Wing OLED 128x32 display, and HMC5883l magnetometer.

I use interrupts on Feather Wing buttons B andC..  The corresponding ISRs just flip a boolean. In loop, I start out with wait for interrupt __wif(), then test if the boolean for ISR C was flipped. If so I display the battery status and delay 2 sec.  In any case I go on to read the magnetometer and display the heading.

As I said, this works. However, if I do not set the SCR SLEEPDEEP bit  to deep sleep, the wfi has no effect. If I add SCR->SCR |= 1<<2; before the wfi it works.  As I read the datasheet, that bit defaults to 0, meaning sleep instead of deep sleep, so I would expect it to sleep if I do nothing, leaving it 0, but it does not wait for the interrupt, just loops through. If I change the instruction to "SCR->SCR &= 11111011;" to set the bit to 0, it also has no effect (no waiting).

The "Cortex-M0 Devices Generic User Guide" says "If your device does not support two sleep modes, the effect of changing the value of this bit is implementation-defined."

Does this mean that the Feather M0 Basic Proto supports only deep sleep but not sleep? If this is the case, what is the implementation definition of changing the value of the bit?

As always, thanks for your help.

Virgil_Machine

Posts: 83
Joined: Sun Apr 13, 2014 6:39 pm

Re: M0 wait for interrupt

The SAMD21 has two categories of low-power mode: IDLE and STANDBY. You select one or the other with the SCR.SLEEPDEEP bit:

- SCR.SLEEPDEEP = 0: IDLE
- SCR.SLEEPDEEP = 1: STANDBY

The IDLE modes have three possible interrupt sources that can still be active: the APB and AHB buses that communicate with the peripherals, and asynchronous interrupts on the pins. Those are controlled by the SLEEP.IDLE bits:

- SLEEP.IDLE = 0x00: CPU clock halted, AHB, APB, & ansyc sources can wake
- SLEEP.IDLE = 0x01: CPU & AHB halted, APB & async can wake
- SLEEP.IDLE = 0x02: CPU, AHB, & APB halted, async can wake

It sounds like you've left the peripheral buses active, and those generate interrupts all the time.

Posts: 42069
Joined: Thu Feb 11, 2010 2:51 pm

Re: M0 wait for interrupt

Mike, thank you very much.

I see the problem. Based on your response I googled a bit and found info on the idle bits that I had not seen before.. What I don't see is how to implement the solution you propose.

If I code SCR.SLEEPDEEP =0; (or one for that matter) the compiler quits on "SCR not defined in scope." I have tried other variants of this, and the only one that compiles is
SCB->SCR &= 11111011;

Further, I have not found any syntax for SLEEP.IDLE = 0X-- that compiles SCR.SLEEP.IDLE, SCB->SLEEP.IDLE blah blah or SCB->SCR.SLEEP.IDLE blah blah don't work either.

Could you point me to something?

Virgil_Machine

Posts: 83
Joined: Sun Apr 13, 2014 6:39 pm

Re: M0 wait for interrupt

The system control block only has the SLEEPDEEP bit. The IDLE register is part of the power manager. I'll post some code when I get home.

mailhouse

Posts: 104
Joined: Sat Jul 25, 2015 8:06 pm

Re: M0 wait for interrupt

Thanks. I have looked through datasheets, through ~\AppData\Local\Arduino15\packages\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\instance\pm.h (also components\pm.h as well as several looks at the datasheet and through a few forums.

I know what to do--it's what Mike said. How to do it is another question. I am not finding specific variable names and constants I can use to set the bits. The closest I come is:
Code: Select all | TOGGLE FULL SIZE
SCB->SCR &= 11111011;  //turn on sleep mode;REG_PM_SLEEP = 0x02; //set SLEEP_IDLE to 2--10 doesn't work either, neither does PM->SLEEP.reg |= 10 (or 0x02)

The device does not sleep. As I said, if I code:
Code: Select all | TOGGLE FULL SIZE
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; //I find this to be more readable than the shift in my original post

it sleeps, and wakes up on interrupt. I can't swear that it's deepsleep (the Feather Wing OLED stays on, displaying the last message sent).

Virgil_Machine

Posts: 83
Joined: Sun Apr 13, 2014 6:39 pm

Re: M0 wait for interrupt

Virgil_Machine wrote:
Code: Select all | TOGGLE FULL SIZE
SCB->SCR &= 11111011;  //turn on sleep mode;

Surely 11111011 above gets interpreted as a decimal number, not a binary one?

I think you're on the right track with SCB_SCR_SLEEPDEEP_Msk:

Code: Select all | TOGGLE FULL SIZE
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;  // Set SLEEPDEEPSCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;  // Unset SLEEPDEEP

As far as setting the idle mode, you might try:

Code: Select all | TOGGLE FULL SIZE
PM->SLEEP.bit.IDLE = 2;  // Set idle mode 2

cmpxchg8b

Posts: 257
Joined: Sat Dec 19, 2015 8:38 pm

Re: M0 wait for interrupt

yeah use a bitmask on the system control register since it holds more than just the SLEEPDEEP bit.
the System Control Register is documented in the ARM Cortex-M0+ Devices Generic User Guide on page 4-13, for what its worth.

Screen Shot 2016-10-10 at 6.10.20 PM.png (63.11 KiB) Viewed 301 times

without the SLEEPDEEP bit set, the power manager defaults to Idle Mode. SLEEPDEEP is another word for "Standby Mode" in atmel-speak.
Code: Select all | TOGGLE FULL SIZE
  //PM->SLEEP.reg |= PM_SLEEP_IDLE_CPU;  //PM->SLEEP.reg |= PM_SLEEP_IDLE_AHB;  PM->SLEEP.reg |= PM_SLEEP_IDLE_APB;

just the CPU is Idle 0
CPU and AHB is Idle 1
CPU, AHB, and APB is Idle 2

you can also turn off clocks individually
Code: Select all | TOGGLE FULL SIZE
PM->APBBMASK.reg &= ~PM_APBBMASK_PORT;

which would remove CLK_PORT_APB from the Power Manager's APB 'B' bit mask, which is to say, the clock for the PORT command on the APB bus.

since the IDE pretty much turns everything on by default
Code: Select all | TOGGLE FULL SIZE
AHBMASK:  CLK_HPBA_AHB CLK_HPBB_AHB CLK_HPBC_AHB CLK_DSU_AHB CLK_NVMCTRL_AHB CLK_DMAC_AHB CLK_USB_AHBAPBAMASK:  CLK_PAC0_APB CLK_PM_APB CLK_SYSCTRL_APB CLK_GCLK_APB CLK_WDT_APB CLK_RTC_APB CLK_EIC_APBAPBBMASK:  CLK_PAC1_APB CLK_DSU_APB CLK_NVMCTRL_APB CLK_PORT_APB CLK_DMAC_APB CLK_USB_APBAPBCMASK:  CLK_SERCOM0_APB CLK_SERCOM1_APB CLK_SERCOM2_APB CLK_SERCOM3_APB CLK_SERCOM4_APB CLK_SERCOM5_APB CLK_TCC0_APB CLK_TCC1_APB CLK_TCC2_APB CLK_TC3_APB CLK_TC4_APB CLK_TC5_APB CLK_ADC_APB CLK_DAC_APB

keep in mind that when the processor is in standby/deep sleep mode, the voltage regulator is in low-power mode too.

mailhouse

Posts: 104
Joined: Sat Jul 25, 2015 8:06 pm

Re: M0 wait for interrupt

Thank you mailhouse and cmpxchg8b.

I had found the information you provided, but needed the syntax that would compile. To restate the original problem: I can get it to wait for interrupt if I set the SCR sleep bit to deep sleep, but not it does not wait if it is set to idle (by default or by explicitly setting it).

It compiles now. I coded:
Code: Select all | TOGGLE FULL SIZE
  SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;

not expecting it to work, because if idle is the default, why would I have to set it? I was right--no wait for interrupt.

Code: Select all | TOGGLE FULL SIZE
 PM->SLEEP.reg |= PM_SLEEP_IDLE_APB;
, following Mike's original instructions and mailhouse's code, but it still has no effect. (BTW, I coded this in setup, in the same place I had the
Code: Select all | TOGGLE FULL SIZE
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
that does work).

So, the question still is: how do I get the Feather M0 into idle mode as opposed to deep sleep (corollary--does the device support idle)?
Specifically, as I asked in the original post (and this is the reason for posting in the Adafruit forum vs. arduino.cc or someplace else):
The "Cortex-M0 Devices Generic User Guide" says "If your device does not support two sleep modes, the effect of changing the value of this bit is implementation-defined."

Does this mean that the Feather M0 Basic Proto supports only deep sleep but not sleep? If this is the case, what is the implementation definition of changing the value of the bit?

Also, in response to
Surely 11111011 above gets interpreted as a decimal number, not a binary one?
re my
Code: Select all | TOGGLE FULL SIZE
SCB->SCR &= 11111011;  //turn on sleep mode;
, I tried specifying id as binary (B'111110111' and hex (0xFB) but neither was accepted. How to I give it a binary? I'm happy with using ~SCB_SCR_SLEEPDEEP_Msk, but I'd like to know...

Virgil_Machine

Posts: 83
Joined: Sun Apr 13, 2014 6:39 pm

Re: M0 wait for interrupt

Virgil_Machine wrote:I can get it to wait for interrupt if I set the SCR sleep bit to deep sleep, but not it does not wait if it is set to idle (by default or by explicitly setting it).

I am not an expert, but are you sure it isn't waiting, and then simply being woken up earlier than you expect by some other interrupt? For example, a timer or serial interrupt managed by the Arduino platform.

Virgil_Machine wrote:I tried specifying id as binary (B'111110111' and hex (0xFB) but neither was accepted. How to I give it a binary?

I don't believe C++ has a standard way of writing a literal binary number.

The GCC compiler, which the Arduino IDE is based on, has a non-standard extension where it accepts the prefix 0b followed by a binary number, like "0b10101010".

Also, the Arduino IDE allows you to write binary prefixed by an uppercase B, like "B10101010". These are just constants #defined in the Arduino header files, and they only work up to 8 bits wide.

cmpxchg8b

Posts: 257
Joined: Sat Dec 19, 2015 8:38 pm

Re: M0 wait for interrupt

are you sure it isn't waiting

Pretty sure. When I code it for deep sleep it performs as excepted: does nothing until a button is pressed. When coded for idle, it loops through continually, taking magnetometer readings and displaying them on the OLED.. There are no Serial operations.

Thanks for the info on binary representation. I'll see if I can get something to work.

Also, I tried your sleep.bit.IDLE syntax, as is and some other experiments using OR--no difference from what I described above.

Virgil_Machine

Posts: 83
Joined: Sun Apr 13, 2014 6:39 pm

Re: M0 wait for interrupt

Virgil_Machine wrote: When coded for idle, it loops through continually, taking magnetometer readings and displaying them on the OLED.

Yeah, but there is, for example, a timer that updates the millisecond counter, which would run at 1000 times per second. Waking up 1000 times per second might feel like continous wakeups when perceived by a human.

Here is a test program I wrote to check the idle sleep:

Code: Select all | TOGGLE FULL SIZE
void setup(){  Serial.begin(9600);}void loop(){  delay(1000);  test(false);  delay(1000);  test(true);}void test(bool sleep){  uint32_t starttime = millis();  uint32_t loopcount = 0;  while (millis() - starttime < 1000)  {    loopcount++;        if (sleep)    {      idleSleep();    }  }  uint32_t endtime = millis();  Serial.print("Test "); Serial.println(sleep ? "with sleep" : "without sleep");  Serial.print(loopcount); Serial.print(" iterations in "); Serial.print(endtime - starttime); Serial.println(" ms.");  Serial.println();}void idleSleep(){  // Select IDLE, not STANDBY, by turning off the SLEEPDEEP bit  SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;    // Select IDLE mode 2 (asynchronous wakeup only)  PM->SLEEP.bit.IDLE = 2;  // ARM WFI (Wait For Interrupt) instruction enters sleep mode  __WFI();}

On my Feather M0 Wifi, I get these results:

Test without sleep
2273574 iterations in 1000 ms.

Test with sleep
1966 iterations in 1000 ms.

So the idle sleep is definitely having an effect.

cmpxchg8b

Posts: 257
Joined: Sat Dec 19, 2015 8:38 pm

Re: M0 wait for interrupt

I believe you, but I'm not sure what would cause the execution to get past the wfi:
Code: Select all | TOGGLE FULL SIZE
void loop() {//the wfi() means we only progress in loop on an interrupt, either button B or C pressed invoking headingISR or batteryISR//If it was C, we display the battery status for 3 seconds and go on//in either case, we display the compass heading, complete the loop, and wait for the next button press   //wait-for-interrupt; this instruction has no effect unless the sleep bit is set in the //System Control Register (SCR)(see setup, in the attachInterrupt area)//if the sleep bit is set, we wait after this instruction for an interrupt __WFI();  //Double underscore!! (took me a few looks to see that)  if (batteryRead)  //if we got here because operator wants battery info (button C pressed--see batteryISR)  {    batteryRead=!batteryRead;  //reset for next pass    float measuredvbat = analogRead(VBATPIN);    measuredvbat *= 2;    // we divided by 2, so multiply back    measuredvbat *= 3.3;  // Multiply by 3.3V, our reference voltage    measuredvbat /= 1024; // convert to voltage    display.clearDisplay();    display.setCursor(0,0);    display.setTextSize(2);  //set size          display.print("VBat: " ); display.println(measuredvbat);    display.setTextSize(1);  //reset size      display.print("Press Button ");display.setTextSize(2); display.println("B");    display.display();    display.setTextSize(1);  //reset size    delay(3000);       //hold for 3 sec, then go on and display heading  } //however we got here (BUTTON_B or BUTTON_C), read the compass and display    /* Get a new sensor event */     sensors_event_t event;     mag.getEvent(&event);    float heading = atan2(event.magnetic.y, event.magnetic.x);    heading+=declinationAngle;  //add declination for location--initialized as a constant    // Correct for when signs are reversed.    if(heading < 0) heading += 2*PI;    // Check for wrap due to addition of declination.    if(heading > 2*PI) heading -= 2*PI;    // Convert radians to degrees for readability.    float headingDegrees = heading * 180/M_PI;    // use degrees to determine text Direction in map function //    Serial.print("Heading (degrees): "); Serial.println(headingDegrees);Serial.print(" = ");Serial.println(mapDirection(headingDegrees));    display.clearDisplay();       display.setCursor(0,0);    display.setTextSize(2); display.print(headingDegrees);display.print("="); display.println(mapDirection(headingDegrees));    display.setTextSize(1);display.print("Press Button ");display.setTextSize(2); display.print("B");    display.display();    display.setTextSize(1);  //reset size//end of loop--back to top to wait for interrupt (next button press)}

Any thoughts?

Thanks.

BTW, SCB->SCR |= 0b00000100; works to set the sleep bit to deep sleep.

Virgil_Machine

Posts: 83
Joined: Sun Apr 13, 2014 6:39 pm

Re: M0 wait for interrupt

Virgil_Machine wrote:I believe you, but I'm not sure what would cause the execution to get past the wfi

Because there are other interrupts happening, and execution will continue past the WFI if any of them fire, not just the interrupts that you installed handlers for.

It looks like one of your ISRs sets a volatile variable named "batteryRead". If I were you, I'd do that from within both ISRs, and then put the WFI inside a while loop that tests your flags. Something like:

Code: Select all | TOGGLE FULL SIZE
// Wait for an interesting interruptwhile (true){  __WFI();  // Wait for next interrupt  if (batteryRead || somethingElse)  {    break;  // The interrupt was one we're interested in, so let's continue with the program  }}

Or just:

Code: Select all | TOGGLE FULL SIZE
// Wait for an interesting interruptwhile (!(batteryRead || somethingElse)){  __WFI();  // Wait for next interrupt}

cmpxchg8b

Posts: 257
Joined: Sat Dec 19, 2015 8:38 pm

Re: M0 wait for interrupt

Got it! I used the second option, testing if either boolean was set by its ISR--if neither was set, wfi. That's the only change I needed to make.

Thank you very much. I learned a whole lot from what was basically a simple project.

FYI, I have written up this whole experience and am am about to post it on my blog: [url]http://virgilmachine.blogspot.com/[url] (give me an hour or so)

Virgil_Machine

Posts: 83
Joined: Sun Apr 13, 2014 6:39 pm