STM32L011 how to reach 1uA in standby mode?
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).
