cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L011 how to reach 1uA in standby mode?

AW.3
Associate II

Hello. I have been getting familiar with the STM32L011F4P6 chip. I am using a 20-pin chip soldered onto a board as my test device. I am using a Nucleo-f429zi as the programming/debug device. I have made the necessary changes and am using the SWD header to connect to my chip and program it. The SWD pins on the Nucleo board are connected to the chip for power and reset as well. I am using an arm-none-eabi-gcc toolchain for development with VSCodium. Everything works smoothly, and I use GDB to load the program onto the chip and run/debug with the help of the st-util tool to connect.

Now, to my question. I have been trying to reach a 1uA standby mode current consumption, but am stuck at 15uA. This does not even match the documentation's consumption level for sleep mode. I am not using the HAL library, and have been taking a 'bare metal' approach. I am using the stm32cubel0 files (including all the CMSIS) and all seems to be working quite well. I have been reading the datasheet and the user's guide to try to identify any steps I may have missed in my process to prepare before sending the chip to standby mode. My 'prepsleep' function is run immediately before the call to the __WFI() function. I can monitor the power consumption using an ammeter (multimeter) by breaking the VDD line from the supply of the Nucleo board to the chip and measuring across it. This shows me that my chip is using around 300-400 uA while running code on start-up at the default 2.1 MHz with the MSI clock. Then I can observe that the chip drops to 15 uA once it reaches the WFI point in the code. What step have I missed? or peripheral or clock not disabled? My prepsleep function looks like this:

void prepsleep(void)
{
   // store current state
   portA |= GPIOA->MODER;
   portB |= GPIOB->MODER;
   portC |= GPIOC->MODER;
 
   GPIOA->PUPDR = 0x24000000;    // port A pull up/down OFF
   GPIOB->PUPDR = 0;             // port B pull up/down OFF
   GPIOC->PUPDR = 0;             // port C pull up/down OFF
   
   // All gpio pins in analog mode
   GPIOA->MODER = 0xffffffff;
   GPIOB->MODER = 0xffffffff;
   GPIOC->MODER = 0xffffffff;
 
   // Disable clock for GPIO Port A
   RCC->IOPENR &= ~RCC_IOPENR_IOPAEN;
   // Disable clock for GPIO Port B
  RCC->IOPENR &= ~RCC_IOPENR_IOPBEN;
   // Disable clock for GPIO Port C
  RCC->IOPENR &= ~RCC_IOPENR_IOPCEN;
 
   /* Enable clock for SYSCFG */
   RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
 
   // Power down ADC
   while(ADC1->CR & ADC_CR_ADSTART){;}             // check no on-going conversions
   ADC1->CR |= ADC_CR_ADDIS;                       // write 1 to ADDIS to disable ADC
   while(ADC1->CR & ADC_CR_ADEN){;}                // wait for enable bit to be OFF
 
   RCC->APB1RSTR = 0xEFFFFFFFU;
   RCC->APB2ENR = 0;                              // turn off all peripheral clocks
   RCC->APB1ENR |= RCC_APB1ENR_PWREN;             // enable PWR interface clock (reduces power consumption in standby)
 
   SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;             // Enable Cortex SLEEPDEEP bit
   RCC->CFGR &= ~RCC_CFGR_STOPWUCK;               // 0=Select MSI clock on exit from stop mode
 
   RCC->CFGR &= ~RCC_CFGR_PPRE2;                  // divide by 1
   // RCC->CFGR |= RCC_CFGR_PPRE2_DIV4;              // divide by 4
   RCC->CFGR |= RCC_CFGR_PPRE2_DIV16;              // divide by 16
   RCC->CFGR &= ~RCC_CFGR_PPRE1;                  // divide by 1
   // RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;              // divide by 4
   RCC->CFGR |= RCC_CFGR_PPRE1_DIV16;              // divide by 16
   RCC->CFGR &= ~RCC_CFGR_HPRE;                   // divide by 1
   // RCC->CFGR |= RCC_CFGR_HPRE_DIV4;              // divide by 4
   RCC->CFGR |= RCC_CFGR_HPRE_DIV512;              // divide by 512
 
   RCC->CR &= ~RCC_CR_PLLON;                       // disable PLL
   while(RCC->CR & RCC_CR_PLLRDY){;}               // wait for PLL to stop
   // RCC->CFGR |= RCC_CFGR_PLLDIV4;                  // PLL clock divider
   // RCC->CR |= RCC_CR_PLLON;                        // enable PLL
   // while(RCC->CR & ~RCC_CR_PLLRDY){;}              // wait for PLL to start
 
   RCC->CSR &= ~RCC_CSR_LSION;                       // disable LSI
   while(RCC->CSR & RCC_CSR_LSIRDY){;}               // wait for LSI to stop
 
   PWR->CR |= PWR_CR_DBP;                            // allow modifying LSEON and RTCEN bits
   RCC->CSR &= ~RCC_CSR_LSEON;                       // disable LSE
   while(RCC->CSR & RCC_CSR_LSERDY){;}               // wait for LSE to stop
   RCC->CSR &= ~RCC_CSR_RTCEN;                       // disable RTC
 
   DBGMCU->CR &= ~DBGMCU_CR_DBG;                      // disable all debug bits (do not keep clocks on fo debugging) 
   PWR->CR &= ~PWR_CR_PVDE;                       // disable PVD
   PWR->CR |= PWR_CR_ULP;                         // ultra-low power (VREFINT off)
   PWR->CR |= PWR_CR_LPSDSR;                      // Switch voltage regulator to low power mode
   // PWR->CR |= PWR_CR_LPDS;                        // Switch voltage regulator to low power mode
   PWR->CR |= PWR_CR_CSBF;                        // Clear the standby flag
   PWR->CR |= PWR_CR_CWUF;                        // Clear the wake-up flag
   PWR->CR |= PWR_CR_PDDS;                        // Power down deep sleep (1=standby, 0=stop mode)
   __NOP();
 
   // Enable clock for SYSCFG
   RCC->APB2ENR &= ~RCC_APB2ENR_SYSCFGEN;
 
   while(PWR->CSR & PWR_CSR_VOSF_Msk){;}          // wait for 0 state
   PWR->CR |= PWR_CR_VOS_Msk;                     // voltage scaling range 3 -> 1.2V
   while(PWR->CSR & PWR_CSR_VOSF_Msk){;}          // wait for 0 state
 
   RCC->ICSCR &= ~(RCC_ICSCR_MSIRANGE_Msk);       // clear MSIRANGE bits -> 65.536 kHz
 
   // power down NVRAM
   FLASH->PDKEYR |= PDKEY1;
   FLASH->PDKEYR |= PDKEY2;
   FLASH->ACR |= FLASH_ACR_RUN_PD;
   RCC->AHBENR &= ~RCC_AHBENR_MIFEN;              // disable NVM clock
 
   RCC->CR &= ~RCC_CR_MSION;                      // disable MSI clock
   while(RCC->CR & RCC_CR_MSIRDY){;}              // wait for MSI to stop
}

Some parts may be unnecessary, and I am uncertain about whether the order I have used is correct (or matters), but from my testing there are some key steps that are definitely working and helping to save power (such as the MSIRANGE and the APB1ENR_PWREN). How do I reach my target of 1uA or less? Any help would be much appreciated.

As a side note, I have also reproduced this using a Nucleo-32 STM32L011K4T6 board with the same results (measuring the current consumption across the tiny jumper pins).

1 ACCEPTED SOLUTION

Accepted Solutions
AW.3
Associate II

After reviewing my 'prepsleep' function and a lot of debugging, I realised that the device was not actually exiting my 'prepsleep' function and not reaching the WFI call (so not entering proper standby).

Thank you to JW for assisting with this. For anyone interested, my revised version of 'prepsleep' is now working. It seems I may have been trying to do some unnecessary steps and/or possibly in the wrong order. My working 'prepsleep' function is below and I am successfully reaching <1uA after calling WFI:

void prepsleep(void)
{
   // GPIOA->PUPDR = 0x24000000;    // port A pull up/down OFF
   // GPIOA->PUPDR = 0;             // port A pull up/down OFF
   GPIOB->PUPDR = 0;             // port B pull up/down OFF
   GPIOC->PUPDR = 0;             // port C pull up/down OFF
   
   // All gpio pins in analog mode
   // GPIOA->MODER = 0xffffffff;
   GPIOB->MODER = 0xffffffff;
   GPIOC->MODER = 0xffffffff;
 
   // Disable clock for GPIO Port A
   // RCC->IOPENR &= ~RCC_IOPENR_IOPAEN;
   // Disable clock for GPIO Port B
   RCC->IOPENR &= ~RCC_IOPENR_IOPBEN;
   // Disable clock for GPIO Port C
   RCC->IOPENR &= ~RCC_IOPENR_IOPCEN;
 
   /* Enable clock for SYSCFG */
   RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
 
   // Power down ADC
   while(ADC1->CR & ADC_CR_ADSTART){;}             // check no on-going conversions
   ADC1->CR |= ADC_CR_ADDIS;                       // write 1 to ADDIS to disable ADC
   while(ADC1->CR & ADC_CR_ADEN){;}                // wait for enable bit to be OFF
 
   // RCC->APB1RSTR = 0xEFFFFFFF;                   // reset all timers (except LPTIM1 - needs checking, not sure this is correct or helps?)
   // RCC->APB2ENR = 0;                              // turn off all peripheral clocks
   RCC->APB1ENR |= RCC_APB1ENR_PWREN;             // enable PWR interface clock (reduces power consumption in standby)
 
   SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;             // Enable Cortex SLEEPDEEP bit
   RCC->CFGR &= ~RCC_CFGR_STOPWUCK;               // 0=Select MSI clock on exit from stop mode
 
   RCC->CFGR &= ~RCC_CFGR_PPRE2;                  // divide by 1
   RCC->CFGR |= RCC_CFGR_PPRE2_DIV16;              // divide by 16
   RCC->CFGR &= ~RCC_CFGR_PPRE1;                  // divide by 1
   RCC->CFGR |= RCC_CFGR_PPRE1_DIV16;              // divide by 16
 
   // RCC->APB2ENR &= ~RCC_APB2ENR_DBGEN;               // disable DBG clock
   // DBGMCU->CR &= ~DBGMCU_CR_DBG;                      // disable all debug bits (do not keep clocks on for debugging) 
   PWR->CR &= ~PWR_CR_PVDE;                       // disable PVD
   PWR->CR |= PWR_CR_ULP;                         // ultra-low power (VREFINT off)
   PWR->CR |= PWR_CR_LPSDSR;                      // Switch voltage regulator to low power mode
   PWR->CR |= PWR_CR_CSBF;                        // Clear the standby flag
   PWR->CR |= PWR_CR_CWUF;                        // Clear the wake-up flag
   PWR->CR |= PWR_CR_PDDS;                        // Power down deep sleep (1=standby, 0=stop mode)
   __NOP();
 
   PWR->CSR |= PWR_CSR_EWUP1 | PWR_CSR_EWUP3;     //  enable wake-up pins
 
   // Disable clock for SYSCFG
   RCC->APB2ENR &= ~RCC_APB2ENR_SYSCFGEN;
 
   while(PWR->CSR & PWR_CSR_VOSF_Msk){;}          // wait for 0 state
   PWR->CR |= PWR_CR_VOS_Msk;                     // voltage scaling range 3 -> 1.2V
   while(PWR->CSR & PWR_CSR_VOSF_Msk){;}          // wait for 0 state
 
   RCC->ICSCR &= ~(RCC_ICSCR_MSIRANGE_Msk);       // clear MSIRANGE bits -> 65.536 kHz
}

I did not switch all the GPIOA pins because I am using some of them for interrupts, but this does not seem to prevent entering standby mode. I am not able to give an accurate current measurement with my equipment but it is less than 1uA.

View solution in original post

6 REPLIES 6

Debugger may keep the processor alive, see DBG_CR register.

Don't use the debugger and perform a power-on reset for the experiment.

JW

AW.3
Associate II

Thanks for the quick answer. I have set the DBG_CR bits in my code so they are not used. I am only using the Nucleo board to supply my chip with the VDD and GND pins for my measurements. Should I be using a completely separate power supply?

> I have set the DBG_CR bits in my code

The debugger may set it to different value "from inside". This is of course toolchain dependentm try to find related documentation or related settings in IDE, if you use one.

> I am only using the Nucleo board to supply my chip with the VDD and GND pins for my measurements.

So it's not connected to a PC with a running debugger? Then it should be fine.

While running the debugger, have you tried to read out content of registers you're writing and verify? For example, you keep almost all APB1 in reset, however, you then enable PWR clock and even write to its registers.

> GPIOA->PUPDR = 0x24000000; // port A pull up/down OFF

0x24000000 doesn't sound like "all off" to me (although it shouldn't matter if its truly Standby, according to RM almost all pins are high impedance).

JW

AW.3
Associate II

I am trying to keep things simple by working on the basis that everything begins at the normal reset values and then I only apply the bare minimum to reach the low current with the WFI call. I agree that GPIOA->PUPDR = 0x24000000 does not seem to be 'all off'. The user guide says that Port A has 0x24000000 as its reset value, while all other ports have 0x0. I wonder if these port A pins relate to the Debug pins? I have just re-run the test setting GPIOA->PUPDR=0, but no difference.

I have left all of APB1ENR in its reset state, since this means no peripheral clocks are enabled. The exception is the PWREN one because if I do not enable this, then my current is 27uA instead of 15uA.

I use the debugger to program the chip, but then I disconnect and use only the power from the Nucleo board to power the chip. I have a simple LED+4k7 resistor to GND on pin PA3 to indicate the program is running. It blinks slowly 3 times, then prepsleep is called, and finally WFI.

AW.3
Associate II

After reviewing my 'prepsleep' function and a lot of debugging, I realised that the device was not actually exiting my 'prepsleep' function and not reaching the WFI call (so not entering proper standby).

Thank you to JW for assisting with this. For anyone interested, my revised version of 'prepsleep' is now working. It seems I may have been trying to do some unnecessary steps and/or possibly in the wrong order. My working 'prepsleep' function is below and I am successfully reaching <1uA after calling WFI:

void prepsleep(void)
{
   // GPIOA->PUPDR = 0x24000000;    // port A pull up/down OFF
   // GPIOA->PUPDR = 0;             // port A pull up/down OFF
   GPIOB->PUPDR = 0;             // port B pull up/down OFF
   GPIOC->PUPDR = 0;             // port C pull up/down OFF
   
   // All gpio pins in analog mode
   // GPIOA->MODER = 0xffffffff;
   GPIOB->MODER = 0xffffffff;
   GPIOC->MODER = 0xffffffff;
 
   // Disable clock for GPIO Port A
   // RCC->IOPENR &= ~RCC_IOPENR_IOPAEN;
   // Disable clock for GPIO Port B
   RCC->IOPENR &= ~RCC_IOPENR_IOPBEN;
   // Disable clock for GPIO Port C
   RCC->IOPENR &= ~RCC_IOPENR_IOPCEN;
 
   /* Enable clock for SYSCFG */
   RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
 
   // Power down ADC
   while(ADC1->CR & ADC_CR_ADSTART){;}             // check no on-going conversions
   ADC1->CR |= ADC_CR_ADDIS;                       // write 1 to ADDIS to disable ADC
   while(ADC1->CR & ADC_CR_ADEN){;}                // wait for enable bit to be OFF
 
   // RCC->APB1RSTR = 0xEFFFFFFF;                   // reset all timers (except LPTIM1 - needs checking, not sure this is correct or helps?)
   // RCC->APB2ENR = 0;                              // turn off all peripheral clocks
   RCC->APB1ENR |= RCC_APB1ENR_PWREN;             // enable PWR interface clock (reduces power consumption in standby)
 
   SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;             // Enable Cortex SLEEPDEEP bit
   RCC->CFGR &= ~RCC_CFGR_STOPWUCK;               // 0=Select MSI clock on exit from stop mode
 
   RCC->CFGR &= ~RCC_CFGR_PPRE2;                  // divide by 1
   RCC->CFGR |= RCC_CFGR_PPRE2_DIV16;              // divide by 16
   RCC->CFGR &= ~RCC_CFGR_PPRE1;                  // divide by 1
   RCC->CFGR |= RCC_CFGR_PPRE1_DIV16;              // divide by 16
 
   // RCC->APB2ENR &= ~RCC_APB2ENR_DBGEN;               // disable DBG clock
   // DBGMCU->CR &= ~DBGMCU_CR_DBG;                      // disable all debug bits (do not keep clocks on for debugging) 
   PWR->CR &= ~PWR_CR_PVDE;                       // disable PVD
   PWR->CR |= PWR_CR_ULP;                         // ultra-low power (VREFINT off)
   PWR->CR |= PWR_CR_LPSDSR;                      // Switch voltage regulator to low power mode
   PWR->CR |= PWR_CR_CSBF;                        // Clear the standby flag
   PWR->CR |= PWR_CR_CWUF;                        // Clear the wake-up flag
   PWR->CR |= PWR_CR_PDDS;                        // Power down deep sleep (1=standby, 0=stop mode)
   __NOP();
 
   PWR->CSR |= PWR_CSR_EWUP1 | PWR_CSR_EWUP3;     //  enable wake-up pins
 
   // Disable clock for SYSCFG
   RCC->APB2ENR &= ~RCC_APB2ENR_SYSCFGEN;
 
   while(PWR->CSR & PWR_CSR_VOSF_Msk){;}          // wait for 0 state
   PWR->CR |= PWR_CR_VOS_Msk;                     // voltage scaling range 3 -> 1.2V
   while(PWR->CSR & PWR_CSR_VOSF_Msk){;}          // wait for 0 state
 
   RCC->ICSCR &= ~(RCC_ICSCR_MSIRANGE_Msk);       // clear MSIRANGE bits -> 65.536 kHz
}

I did not switch all the GPIOA pins because I am using some of them for interrupts, but this does not seem to prevent entering standby mode. I am not able to give an accurate current measurement with my equipment but it is less than 1uA.

Thanks for coming back with the solution. Please select your post as Best so that thread is marked as solved.

JW