Skip to content
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

Making Low Power great again #51

Open
mamama1 opened this issue Jun 17, 2022 · 6 comments
Open

Making Low Power great again #51

mamama1 opened this issue Jun 17, 2022 · 6 comments
Labels
topic: code Related to content of the project itself type: enhancement Proposed improvement

Comments

@mamama1
Copy link

mamama1 commented Jun 17, 2022

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

  • Deep sleep of the SAMD21G18
  • Sleep current at around 3µA
  • Wake up from sleep with external interrupt (button press) WITH edge detection (rising, falling or both edges)
  • No floating pins, therefore no unnecessary current draw during sleep (hundreds of microamps!)
  • no breaking of Arduino IO handling, all the pins are still in default (INPUT) state on initialization and during program execution

The issues:

  • The SAMD21G18 can only wake up with edge detection, if the external interrupt controller (EIC) is being clocked during sleep. For some reason, the LowPower library does not do this, although there is a OSCULP32K oscillator inside the MCU which runs all the time anyway and no matter what (even in the deepest of sleep modes).
  • The deep sleep current varies extremely if the pins are floating, which is the case for ALL pins by default with Arduino (all are set to INPUT at initialization). You'll see anything from 100µA to 500µA just because of the floating pins. Of course, one could just disconnect all the pins but with that there come two problems: 1) you can't wake up the MCU by bringing a pin low (i.e. pressing a button), and 2) all the pin config is lost (which pins are output, which are input, which are pulled up or down and which are muxed to peripherals) if you don't bother to save the registers somewhere first.
  • It is possible to disable the functionality that the Arduino core puts all pins into INPUT mode on initialization but apparently that breaks stuff
  • UART RX pins as well as the MISO pin are INPUT pins so I had to configure them as input pullups (for MISO, at least during sleep!)

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.

@per1234 per1234 added type: enhancement Proposed improvement topic: code Related to content of the project itself labels Jun 18, 2022
@denisbaylor
Copy link

denisbaylor commented Nov 3, 2022

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
Copy link

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
Copy link

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

@mamama1
Copy link
Author

mamama1 commented Jun 19, 2024

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
Copy link

Andynopol commented Jun 19, 2024

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
Copy link

Any idea why is SAMD21 dead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: code Related to content of the project itself type: enhancement Proposed improvement
Projects
None yet
Development

No branches or pull requests

4 participants