logo_elektromys.eu

/ STM32L0 Low-power režimy I|

Rodina STM32L0 je, jak jistě víte, určena pro "low-power" aplikace a asi nemá smysl ji nějak obšírně představovat. Mě osobně k ní přivedla kombinace několika faktorů. Byla to velmi příznivá cena variant v TSSOP pouzdře (STM32L011F4 lze koupit za přibližně 25kč) a neblahé zkušenosti se spotřebou 8bitových Attiny 1-series. Techniky, které může STM32L0 používat k optimalizaci spotřeby je celá řada a já s nimi nemám dost praktických zkušeností. Proto budu tento krátký tutoriál věnovat postupům jak snížit odběr v tzv. aktivním režimu (chcete-li "bdělém" stavu). Mnohdy totiž stačí srazit spotřebu na desítky uA a není třeba "trápit se" s režimy spánku (s nimiž je spotřeba ještě o řád nižší).

Stejně jako u většiny MCU má největší vliv na spotřebu taktovací frekvence ("clock"). Datasheet k STM32L011 na níž budu experimentovat uvádí spotřebu "až" 76uA/MHz (která je od praktické reality ale hodně vzdálená). Jako zdroj clocku máme krom 16MHz HSI (případně PLL), také MSI (Medium Speed) RC oscilátor. Jeho frekvenci je možné přepínat v sedmi krocích od 65kHz do 4.2MHz. Další snižování taktu jádru nebo periferiím lze docílit děličkami AHB a APB jako u všech STM. Slušnou kontrolu máme také nad vnitřním napěťovým regulátorem. Můžeme vybírat napětí pro digitální obvody čipu mezi 1.8V (tzv. Range1), 1.5V (Range2) a 1.2V (Range3). Se snižováním napětí klesá odběr a krom toho se také snižuje minimální napájecí napětí na kterém lze čip provozovat. Nad to všechno lze regulátor přepnout do tzv. Low-Power Run (LPR) módu a opět tím ušetřit další desítky uA. No a já si dovolím v několika krátkých prográmcích tyto funkce vyzkoušet a změřit při tom odběr. Veškeré ovládání je jednoduché a jedinou komplikací je zorientovat se v omezeních kdy lze kterou funkci využít. Což v praxi znamená posbírat podmínky různě roztroušené po datasheetu a nebo věřit, že jsem je správně shrnul v následujícím odstavci.

Omezení

Vysvětlivky:
- Zakázaná konfigurace
WS "Wait state"
LPR Low power run

PLL výstupní frekvence PLL
Vdd Napájecí napětí čipu
Napětí /
Frekvence
Range 1
Vdd > 1.71V
Range 2
Vdd > 1.65V
Range 3
Vdd > 1.65V
f > 16MHz 1WS - -
f = 8~16MHz 0WS 1WS -
f = 4.2~8MHz 0WS 0WS 1WS
f < 4.2MHz 0WS 0WS 0WS
f < 132kHz 0WS 0WS, LPR 0WS
Další omezení PLL<=96MHz PLL<=48MHz PLL<=24MHz,
Nelze mazat a
zapisovat Flash

Testy

Další teorií vás nudit nebudu a rovnou se pustím do jednoduchých testů. Pro jednoznačnost zrekapituluji. Pracujeme na čipu STM32L011. Vzorové kódy využívají "LL" knihoven (HAL nemusím a STDper pro L0 nejsou). Napájecí napětí je 3.3V pokud nenapíšu jinak. Abychom mohli v různých režimech srovnávat spotřebu budeme ve všech příkladech blikat LEDkou na PA5 v rytmu 5Hz (tedy každých 100ms přepneme stav LED). Celý zdrojový kód je ke stažení v odkazech a nudnými kusy kódu (inicializaci pinů) vás nebudu obtěžovat. Čekací smyčku realizuji pomocí Systicku. Funkce LL_Init1msTick(SystemCoreClock) naplní strop Systicku odpovídající hodnotou aby přetékal každou milisekundu. Funkce LL_mDelay() pak vyčká odpovídající počet přetečení Systicku. Obě funkce jsou z knihovny "...utils.h". Protože se budeme během některých ukázek pohybovat s odběrem v řádu desítek uA, tak pro lepší objektivitu výsledků nastavíme všechny nepoužité piny (krom SWD rozhraní) do analogového módu. Výsledné spotřeby pro každou konfiguraci jsem shrnul do tabulky v závěru.

32MHz z PLL

V první ukázce rozběhneme čip na maximální frekvenci. V knihovně "...utils.h", je funkce LL_PLL_ConfigSystemClock_HSI(), která zjednodušuje nastavení clocku. Předhodíte ji parametry PLL a děliček pro APB a AHB a ona sama nastaví odpovídající latenci pro flash paměť, spustí PLL, přepne ho jako systémový clock (sysclk) a nastaví děličky. Z návratové hodnoty můžete zjistit zda proběhla úspěšně. Před jejím zavoláním si musíte pohlídat několik věcí sami. V prvé řadě musíte sami nastavit napěťový regulátor do Range 1 (1.8V), protože po startu čip běží v Range 2 (s clockem 2 MHz z MSI). Konfiguraci napětí provádí funkce LL_PWR_SetRegulVoltageScaling() jejíž argumenty jsou "...SCALE1" až "...SCALE3". Což je totéž jako Range 1 až Range 3 o nichž už padla zmínka. Škoda, že knihovny nedodržují terminologii z datasheetu. Přirozeně před voláním funkcí z PWR knihoven musíte do PWR pustit clock (jako všemu na STM). Před změnou clocku je potřeba ověřit že je regulátor v Range 1 a případně počkat (přepnutí může nějakou dobu trvat). Zda změna probíhá se můžete dozvědět z funkce/vlajky LL_PWR_IsActiveFlag_VOS().

// Taktujeme čip pomocí PLL na "plný výkon" (32MHz)
void clock_32MHz(void){
 ErrorStatus status;
 LL_UTILS_PLLInitTypeDef pll; // struktura pro konfiguraci PLL
 LL_UTILS_ClkInitTypeDef clk; // struktura pro konfiguraci AHB a APB děliček
 // PLLCLK = 16MHz * 4 / 2 = 32MHz (PLLVCO = 16*4 = 64MHz)
 pll.PLLDiv = LL_RCC_PLL_DIV_2; // 16MHz HSI dělíme 2 na 8MHz pro PLL
 pll.PLLMul = LL_RCC_PLL_MUL_4; // 8MHz pro PLL násobíme 4 na 32MHz
 clk.AHBCLKDivider = LL_RCC_SYSCLK_DIV_1; // Sysclkock nedělit 
 clk.APB1CLKDivider = LL_RCC_APB1_DIV_1; // clock pro periferie nedělit
 clk.APB2CLKDivider = LL_RCC_APB2_DIV_1; // clock pro periferie nedělit
 // Přepneme regulátor do Range1 (Výchozí je Range2)
 LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); // clock pro PWR kontrolér
 LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1); // 1.8V pro jádro
 while(LL_PWR_IsActiveFlag_VOS()); // počkáme než změna napětí proběhne
 // zavoláme konfirugraci clocku (fce spustí PLL, nastaví latenci, přepne Sysclock na PLL)
 status=LL_PLL_ConfigSystemClock_HSI(&pll,&clk);
 // pokud vše proběhlo úspěšně, běžíme na 32MHz
 if(status==SUCCESS){SystemCoreClock = 32000000UL;}
}

16MHz z HSI

Taktovat čip z vnitřního 16MHz oscilátoru je velice jednoduchá záležitost. Stačí jej pomocí LL_RCC_HSI_Enable() zapnout, vyčkat než naběhne (indikuje funkce LL_RCC_HSI_IsReady()), nastavit latenci flash paměti pomocí LL_FLASH_SetLatency() a přepnout systémový clock pomocí LL_RCC_SetSysClkSource(). Protože nekonfiguruji napětí jádra, počítám s tím že čip běží s výchozí konfigurací, tedy v Range 2 (proto také nastavuji 1 Wait state).

// Taktujeme 16MHz z HSI, čip ve výchozím natavení (regulátor v Range 2)
void clock_16MHz(void){
 // rozběhnout HSI a počkat na jeho stabilizaci
 while(!LL_RCC_HSI_IsReady()){LL_RCC_HSI_Enable();}
 LL_RCC_HSI_DisableDivider(); // clock z HSI nedělit
 LL_FLASH_SetLatency(LL_FLASH_LATENCY_1); // V Range 2 je potřeba 1WS (v Range 1 stačí 0WS)
 LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI); // přepnout systémový clock
 SystemCoreClock = HSI_VALUE;
}

Bude-li někomu z vás vadit latence flash paměti, může (nám už známým postupem) přepnout čip do Range 1. Takže jen pro kompletnost uvádím zdrojový kód. Jestli to má nějaké rozumné opodstatnění netuším, ale zajímalo mě jak to zahýbe se spotřebou.

// Taktujeme 16MHz z HSI, regulátor napětí přepneme do Range 1 (1.8V)
void clock_16MHzR1(void){
 // rozběhnout HSI a počkat na jeho stabilizaci
 while(!LL_RCC_HSI_IsReady()){LL_RCC_HSI_Enable();}
 LL_RCC_HSI_DisableDivider();  // clock z HSI nedělit
 // Přepnout napěťový regulátor do Range 1
 LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); // clock pro PWR
 LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1); // Nastavit napětí 1.8V 
 while(LL_PWR_IsActiveFlag_VOS());  // počkat než se regulátor přepne
 LL_FLASH_SetLatency(LL_FLASH_LATENCY_0); // v Range 1 stačí 0WS (což je výchozí hodnota)
 LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI); // přepnout systémový clock 
 SystemCoreClock = HSI_VALUE;
}

Dovolím si věnovat malou pozornost děličce za HSI. Funkcí LL_RCC_HSI_EnableDivider() a LL_RCC_HSI_DisableDivider() můžete zapnout nebo vypnout děličku čtyřmi. Zajímavá bude asi hlavně v případě kdy HSI nepoužíváte jako clock pro jádro, ale pro LPTIM, LPUART nebo I2C.

1MHz z MSI

Nadpis je lehce zavádějící, protože MSI oscilátor má ve svém výběru frekvenci 1.048MHz, nikoli 1MHz (ale to by se hodně špatně četlo). Konfigurace je naprosto triviální. Čip má totiž po startu rozběhnutý MSI na frekvenci 2.096MHz (a bere z něj clock). Takže stačí pomocí funkce LL_RCC_MSI_SetRange() zvolit požadovanou frekvenci. Pokud nechceme do SystemCoreClock zapisovat frekvenci ručně, můžeme si pomoc makrem __LL_RCC_CALC_MSI_FREQ(), které ji za nás dopočítá. Když si ho prohlédnete, zjistíte, že frekvence MSI jsou násobky známé konstanty 65536 a tedy mocniny dvou.

// Taktuje čip 1.048MHz z MSI (MCU ve výchozím nastavení)
void clock_1MHz(void){
 LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_4); // zvolíme frekvenci MSI (1.048MHz)
 //while(!LL_RCC_MSI_IsReady()){LL_RCC_MSI_Enable();} // rozběhne MSI (lze vynechat pokud je čip ve výchozí konfiguraci - po startu)
 //LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_MSI); // přepne zdroj clocku na MSI (opět lze vynechat ve výchozí konfiguraci)
 //LL_FLASH_SetLatency(LL_FLASH_LATENCY_0); // Nastaví 0 Wait state pro flash (opět lze vynechat ve výchozí konfiguraci)  
 SystemCoreClock = __LL_RCC_CALC_MSI_FREQ(LL_RCC_MSIRANGE_4);
}

132kHz z MSI

Při naší cestě od vysokých taktovacích frekvencí k nižším, míjíme 132kHz. Tahle frekvence je zajímavá protože na ní už smíme přepnout regulátor do low power režimu. To lze provést funkcí LL_PWR_EnterLowPowerRunMode(). Před jejím voláním je ale dobré ohlídat si že je regulátor v Range 2. Zvláště pokud jste ho před tím měnili, jinak je v tomto režimu už po startu.

// Taktujeme čip na ~32.768kHz, regulátor v low-power, regulátor by měl být v Range 2 
void clock_131kHz(void){
 LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_1); // zvolíme frekvenci MSI (132kz)
 //while(!LL_RCC_MSI_IsReady()){LL_RCC_MSI_Enable();} // rozběhne MSI (lze vynechat pokud je čip ve výchozí konfiguraci - po startu)
 //LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_MSI); // přepne zdroj clocku na MSI (opět lze vynechat ve výchozí konfiguraci)
 // Low power mód regulátoru lze zapnout jen v Range 2 (což je výchozí volba po startu čipu)
 //LL_FLASH_SetLatency(LL_FLASH_LATENCY_0); // Nastaví 0 Wait state pro flash (opět lze vynechat ve výchozí konfiguraci)
 LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);  // clock pro PWR doménu
 LL_PWR_EnterLowPowerRunMode(); // Přepneme regulátor do Low power módu (teoreticky teď máme limitované množství aktivních periferií)
 SystemCoreClock = __LL_RCC_CALC_MSI_FREQ(LL_RCC_MSIRANGE_1);
}

32.768kHz z MSI

Postupné snižování frekvence zakončíme na 32.768kHz. Další snižování ztrácí smysl, neboť dynamická složka odběru (závislá na frekvenci) je zanedbatelná ve srovnání se statickou složkou. Dál už na nás čekají jen režimy spánku nebo snižování napájecího napětí. Protože minimální frekvence MSI je 65.536kHz musíme využít AHB děličky a dělit dvěma. Opět můžeme regulátor přepnout do low power režimu. Další mikroampéru můžeme ušetřit deaktivací interní reference VREFINT. Po volání funkce LL_PWR_EnableUltraLowPower() bude během low power režimu (v němž běžíme) reference vypnutá. Tím přicházíme o BOR, PVD a interní teplotní senzor (což nám v tomto případě nevadí).

// Taktujeme čip na ~32.768kHz, regulátor v low-power, regulátor by měl být v Range 2 
void clock_32kHz(void){
 LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_0); // přepneme MSI na 65.536kHz
 //while(!LL_RCC_MSI_IsReady()){LL_RCC_MSI_Enable();} // rozběhne MSI (lze vynechat pokud je čip ve výchozí konfiguraci - po startu)
 //LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_MSI); // přepne zdroj clocku na MSI (opět lze vynechat ve výchozí konfiguraci)
 //LL_FLASH_SetLatency(LL_FLASH_LATENCY_0); // Nastaví 0 Wait state pro flash (opět lze vynechat ve výchozí konfiguraci)
 LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_2); // Sysclock = 65.536/2 = 32.768kHz
 LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); // clock pro PWR doménu
 LL_PWR_EnterLowPowerRunMode();  // Přepneme regulátor do Low power módu
 SystemCoreClock = __LL_RCC_CALC_MSI_FREQ(LL_RCC_MSIRANGE_0) / 2; // frekvence jádra 
 LL_PWR_EnableUltraLowPower(); // Vypneme interní referenci VREFINT
}

Vyhodnocení

No a jak to všechno dopadlo ? Řekl bych že docela dobře. Protože STM poráží v těchto režimech ve spotřebě i 8bitové MCU řady Attiny.
Namátkou při 1MHz a 3.3V se dle datasheetu spotřeby pohybují:
Attiny24 - 600uA
Attiny13 - 500uA
Attiny1614 - 260uA
Srovnejte to s níže uvedenými výsledky našich pokusů.

Spotřeba v aktivích režimech
Frekvence Zdroj
clocku
Napětí
regulátoru
Napájecí
napětí
spotřeba poznámka
32MHz PLL(HSI) Range 1 3.3V 4.55mA
16MHz HSI Range 2 3.3V 1.96mA
16MHz HSI Range 1 3.3V 2.66mA
4MHz HSI Range 3 3.3V 0.63mA
1MHz MSI Range 3 3.3V 246uA
1MHz MSI Range 3 3.0V 208uA
1MHz MSI Range 3 2.5V 186uA
1MHz MSI Range 3 1.8V 158uA
131kHz MSI Range 3 3.3V 44uA
131kHz MSI Range 2 3.3V 35uA Low Power Run
32kHz MSI Range 2 3.3V 21.1uA Low Power Run
32kHz MSI Range 2 3.3V 20.6uA Low Power Run + ULP
32kHz MSI Range 2 3.0V 20.0uA Low Power Run + ULP
32kHz MSI Range 2 1.8V 19.4uA Low Power Run + ULP

Závěrečné poznámky

STM32L011 na "bastldesce". Vyvedený reset na tlačítko a programátor je pro low-power experimenty víc než dobrý nápad.

| Odkazy /

Home
| V1.01 13.3.2019 /
| By Michal Dudka (m.dudka@seznam.cz) /