arduino-libraries / ArduinoLowPower

Powersave features for SAMD boards
GNU Lesser General Public License v2.1
80 stars 57 forks source link

Making Low Power great again #51

Open mamama1 opened 2 years ago

mamama1 commented 2 years ago

After days of work (I am a noob), I have finally achieved the following:

The issues:

So, what I have done to achieve my goals (actually not too complex):

In Setup(), first thing to do for me was to attach OCSULP32K to GCLK2:

GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(2) | GCLK_GENCTRL_DIVSEL );
    while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)

Then setup your pins according to your needs, I had some buttons connected:

pinMode(BTN2, INPUT_PULLUP);
pinMode(BTN3, INPUT_PULLUP);
pinMode(BTN4, INPUT_PULLUP);

Set the UART RX pins to INPUT_PULLUP as well

pinMode(0, INPUT_PULLUP);
pinMode(31, INPUT_PULLUP);

Attach my interrupt with an ISR on falling edge: attachInterrupt(this->ButtonPin, myISR, FALLING);

Now comes the fat part, I wrote a sleep function which 1) saves all the pin configs 2) sets all pins which currently are INPUTs to INPUT_PULLUPs 3) sets MISO to INPUT_PULLUP (there might be more pins for which this should be done) and which puts the MCU to sleep and then after wakeup restores all the registers so the user program isn't affected by misconfigured GPIO pins.

    // Configure External Interrupt Controller (EIC) to GCLK2 which has been connected to OSCULP32K above, which in turn is ALWAYS running, even in deep sleep (can't be turned off)
    // It seems that this MUST happen AFTER the interrupt has been attached
    GCLK->CLKCTRL.reg = uint16_t(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | GCLK_CLKCTRL_ID( GCLK_CLKCTRL_ID_EIC_Val ) );
    while (GCLK->STATUS.bit.SYNCBUSY) {}

    USBDevice.detach();
    USBDevice.end();
    USBDevice.standby();

    Serial.println();

    for (uint8_t n = 0; n < PORT_GROUPS; n++)
    {
        regDir[n] = PORT->Group[n].DIR.reg;
        regOut[n] = PORT->Group[n].OUT.reg;
    }

    // Save current pin config and set all INPUT pins to INPUT_PULLUP
    for (uint8_t n=0; n<NUM_DIGITAL_PINS; n++)
    {
        this->regPinCFG[n] = PORT->Group[g_APinDescription[n].ulPort].PINCFG[g_APinDescription[n].ulPin].reg;

        if ((PORT->Group[g_APinDescription[n].ulPort].PINCFG[g_APinDescription[n].ulPin].reg & (uint8_t)(PORT_PINCFG_INEN)) == (uint8_t)(PORT_PINCFG_INEN) &&
            (PORT->Group[g_APinDescription[n].ulPort].DIR.reg & (uint32_t)(1<<g_APinDescription[n].ulPin)) == 0 &&
            (PORT->Group[g_APinDescription[n].ulPort].PINCFG[g_APinDescription[n].ulPin].reg & (uint8_t)(PORT_PINCFG_PULLEN)) == 0)
        {
            pinMode(n, INPUT_PULLUP);
        }
    }

    // Save MISO pin config
    this->pinSPICFG[0] = PORT->Group[g_APinDescription[PIN_SPI_MISO].ulPort].PINCFG[g_APinDescription[PIN_SPI_MISO].ulPin].reg;

    // Set MISO pin to non-floating states
    pinMode(PIN_SPI_MISO, INPUT_PULLUP);

    SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; //cth to fix hangs

    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    __DSB();
    __WFI();

    SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; //restore

    for (uint8_t n = 0; n < PORT_GROUPS; n++)
    {
        PORT->Group[n].DIR.reg = regDir[n];
        PORT->Group[n].OUT.reg = regOut[n];
    }

    // Restore previously saved pin config
    for (uint8_t n=0; n<NUM_DIGITAL_PINS; n++)
    {
        PORT->Group[g_APinDescription[n].ulPort].PINCFG[g_APinDescription[n].ulPin].reg = this->regPinCFG[n];
    }

    // Restore previously saved MISO pin config
    PORT->Group[g_APinDescription[PIN_SPI_MISO].ulPort].PINCFG[g_APinDescription[PIN_SPI_MISO].ulPin].reg = this->pinSPICFG[0];

    USBDevice.init();
    USBDevice.attach();

Most probably, to reach library quality and broad compatibility with all the possible configurations and SAMD21 boards, this needs to be refined very much but as I said: I am a noob and this took me days to find out and to do.

I would be very happy to see the fixes refined and implemented into the LowPower library because in the current state, with floating pins, only level detection external interrupts and so on, in my humble opinion this is a little bit useless for real low power applications.

I mean, the SAMD21 is a way more powerful MCU than the AVR (328p for example) and still it can achieve way lower standby power consumption and I think that it is a pity that this potential is kind of wasted.

Thank you for your attention.

denisbaylor commented 1 year ago

Nice work mamama1. I found your comment interesting:

// Configure External Interrupt Controller (EIC) to GCLK2 which has been connected to OSCULP32K above, which in turn is ALWAYS running, even in deep sleep (can't be turned off)
// It seems that this MUST happen AFTER the interrupt has been attached

I think the reason that you have to do this after the interrupt has been attached is that attaching an interrupt calls the broken function configGCLK6() which corrupts the most recently configured clock. If you've just configured GCLK2 before attaching the interrupt, GCLK2 gets corrupted.

See issue #30.

I recommend patching my suggested fix in issue #30 if you're going to use this library.

Andynopol commented 1 week ago

I recommend forking this project. It's been like 3 years since your issue @denisbaylor and still it's not merged.

Deep sleep is one the THE MOST IMPORTANT aspect of MCU's since IoT is comming and comming fast, yet one of the less supported.

If any of you guys are willing to fork this library or make a sleep library from scratch for SAMD21 let me know, I would love to work with you guys!

Andynopol commented 1 week ago

@mamama1 I would love to see your entire code base. I have a small project going on where I could use your example.

mamama1 commented 1 week ago

seems like SAMD21 development is kind of dead. I haven't touched anything since 1,5 years ago and I can hardly remember anything. I'm sorry, but I think I've given away everything in my first post. I won't get better from there.

Andynopol commented 1 week ago

From your first post I don't know who is this and who is regDir. That is why I am asking. You've created an external class and just initiated it in setup?

Andynopol commented 1 week ago

Any idea why is SAMD21 dead?