V následující kapitole se budeme věnovat základním informacím o spotřebě MCU a technikám jak ji lze na STM8S optimalizovat. Mějte na paměti že spotřeba vašeho zařízení je ovlivněna mnoha faktory a samotné MCU často tvoří jen její zlomek. Jinými slovy chci říct že typicky nemá smysl zabývat se laděním firmwaru které ušetří jednotky mA u zařízení s odběrem řádově vyšším. Při optimalizaci spotřeby proto vždy postupujte od největších žroutů a řešte nejprve ty faktory, které jdou vyřešit nejjednodušeji. Například běží-li váš mikropočítač se spotřebou 2mA z napětí dodávaného lineárním regulátorem jehož klidová spotřeba je 5mA, je výhodnější vyměnit regulátor než optimalizovat spotřebu MCU. Než se tedy pustíte do úprav firmwaru vždy si pečlivě projděte celý systém. Ideálně na to myslete už při návrhu zařízení a výběru komponent. Pro zbytek článku budu pod pojmem spotřeba myslet proudový odběr (nikoli příkon ve Watech).
Na spotřebu v podstatě jakéhokoli MCU má vliv:
Většina mikrokontrolérů umožňuje pro optimalizaci různě měnit taktovací frekvence, zapínat a vypínat jednotlivé periferie a má taky schopnost "usnout". Spánek bývá různě hluboký podle toho co všechno je v MCU vypnuté a jaké různé událosi a za jak dlouho mohou MCU ze spánku probudit. Obecně platí že čím je spánek hlubší tím je spotřeba nižší. Ale taky platí že čím má být spánek hlubší tím méně je signálů, které mohou MCU probudit. A taky platí že spotřeba roste pokud potřebujeme MCU ze spánku probudit rychle. Různé režimy spánku jsou pak různými kompromisy mezi těmito třemi pravidly. Jinak řečeno, potřebujete-li se probudit rychle, bude vás to něco stát na zvýšené spotřebě (například bude nutné ve spánku nechat v provozu oscilátor, neboť jeho rozběh je zdlouhavý). Potřebujete-li aby vás mohl probudit nějaký specifický signál (například příchozí byte po UARTu) opět za to zaplatíte spotřebou (neboť musí běžet UART). Tohle platí jak u STM8, STM32, AVR a nejspíš u valné většiny jiných MCU.
První a jednoduchá možnost jak ovlivnit spotřebu je správně volit taktovací frekvenci. Do teď asi většina vašich aplikací běžela na 16MHz, tedy maximální frekvenci kterou poskytuje interní oscilátor. Za to jste platili vysokou spotřebou, která vás nejspíš ale nezajímala. Často vaše aplikace nedělala vůbec nic zajímavého a drtivou většinu času jen čekala ve smyčkách na správný okamžik kdy něco vykoná. Pokud jste například každou sekundu měřili teplotu a zobrazovali ji na displeji, tak vaše aplikace nejspíš strávila 999ms čekáním a 1ms vykonávala užitečnou činnost v podobě čtení teploty a zápisu na displej. Po celou tu dobu měla stejnou spotřebu a tutéž úlohu by hravě zvládla i na 1MHz, jen by jí převod teploty a zápis na displej trval o pár milisekund déle. Na 1MHz by ale spotřeba MCU byla výrazně nižší (třeba 5x). A ve skutečnosti by jí stačilo i ještě mnohem méně než 1MHz.
Frekvenci interního oscilátoru můžeme měnit funkcí CLK_HSIPrescalerConfig() a dělící poměr lze volit 1x (16MHz), 2x (8MHz), 4x (4MHz) a 8x (2MHz). Na této frekvenci pak běží všechny periferie. Jádro (CPU) má k dispozici ještě další děličku, která může dělit až 128x a je tak možné CPU "podtaktovat" zatímco periferie běží rychleji. K jejímu nastavení slouží funkce CLK_SYSCLKConfig(). V praxi budete mít ale jen velmi zřídka potřebu podtaktovat CPU. Jak si totiž ukážeme později, budeme ho moct kompletně vypnout. Nebudu se tedy možností "podtaktování" CPU zabývat a zůstanu jen u změn frekvence interního oscilátoru. Pro představu při 5V a 16MHz bez jakýchkoli dalších tecnik má STM8S spotřebu přibližně 5.25mA. Při 2MHz okolo 1.3mA. Pokud aplikaci taktujete externím krystalem, nemůžete jeho frekvenci softwarově měnit a zbývá už jen zmíněná možnost "podtaktování" CPU. Plánujete-li tedy v takovém případě optimalizovat spotřebu, myslete na to už při výběru krystalu.
Další efektivní a hlavně jednoduchou technikou řízení spotřeby, je vypínání a zapínání periferií (PCG - Peripheral Clock Gating). Nepoužívanou periferii můžete vypnout (vypnout jí "clock"). Případně když periferii používáte jen zřídka tak si ji zapnout jen když ji zrovna potřebujete. Po startu jsou na STM8S všechny periferie zapnuté, není tedy od věci je hned po startu zase vypnout (samozřejmě krom těch které plánujete použít). K zapínání/vypínání existuje knihovní funkce, ale je mizerná, protože umí na jedno zavolání zapnout nebo vypnout jen jednu periferii. Chcete-li tedy po startu vypnout všechny, musíte funkci zavolat asi 10x. Takový postup kazí čitelnost programu, plýtvá pamětí, je pomalý nebo prostě škaredý. Raději tedy budeme PCG ovládat přímo přes registry. Registry pro zapínání/vypínání periferií se jmenují PCKENR1 a PCKENR2. Reference manuál je rozebírá v sekci Clock Control a prostě jednička v příslušném bitu v registru zapíná vybranou periferii. Zapisovat do nich můžete jednoduše příkazem:
CLK->PCKENR1 = ...
Pro dobrou čitelnost je vhodné používat makra (masky jednotlivých bitů) z stm8s.h jako třeba
CLK_PCKENR1_TIM4 atp.
Vliv na spotřebu je citelný, zvláště pokud aplikace běží na vyšší frekvenci (neboť jak jsme si řekli spotřeba i periferií je závislá na frekvenci). Například při 16MHz a 5V je činí spotřeba nepoužívaných ale běžících periferií 0.67mA z 5.25mA, tedy cca 13%. Při vypínání ale dávejte pozor na to kterou periferii používáte. Ne vždy je to totiž jasné, například můžete zapomenout, že naše funkce milis používá TIM4.
Nejefektivnější technikou je režim spánku. U STM8S existují dva - mělký Wait a hluboký Halt, kterým se teď ale nebudeme zabývat. V režimu Wait se zastaví clock pro CPU. Oscilátor i všechny ostatní (zapnuté) periferie běží dál. Díky tomu může CPU probudit jakékoli přerušení a máme tedy široké spektrum možných zdrojů probuzení. Samotné probuzení je relativně rychlé (při 16MHz by mělo proběhnout za 4.3us). K uspání slouží příkaz wfi(). Programy napsané neblokujícím způsobem jsou často v takové podobě, že lze příkaz wfi() snadno přidat do hlavní smyčky bez jakékoli další potřeby upravovat kód aniž by to nějak ohrozilo funkci.
Teď by to chtělo nějakou ukázku. Sestavil jsem modelový příklad, který každých 5 sekund blikne na 0.2s LEDkou. Dlouhý interval 5s jsem zvolil proto, že pak mohu spolehlivě změřit odběr. Blikání bude obsluhovat funkce process_LED() napsaná klasicky neblokujícím způsobem pomocí milis. Funkce jen střídavě "čeká" 4.8s a 0.2s a zapíná, případně vypíná LEDku na pinu PD4.
void process_LED(void){ static uint8_t phase=0; static uint16_t last=0; if(phase == 0){ if(milis() - last > 4800){ last = milis(); GPIO_WriteHigh(GPIOD, GPIO_PIN_4); // LED ON phase = 1; } }else{ if(milis() - last > 200){ last = milis(); GPIO_WriteLow(GPIOD, GPIO_PIN_4); // LED OFF phase = 0; } } }
V první ukázce žádný režim snížení spotřeby nepoužijeme a necháme aplikaci běžet na 16MHz. Při napájení napětím 5V je spotřeba STM8S103 5.25mA.
// A) STM8S103, 5V, 16MHz, žádná technika snížení spotřeby - 5.25mA void main(void){ CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 16MHz pro CPU i Periferie init_milis(); // rozběhnout milis (TIM4) GPIO_Init(GPIOD,GPIO_PIN_4,GPIO_MODE_OUT_PP_LOW_SLOW); // Výstup na LEDku while(1){ process_LED(); // obsluhuj jen LEDku } }
V druhé ukázce využijeme PCG a vypneme všechny periferie kromě Timeru4, který zajišťuje chod milis. Spotřeba spadne na 4.58mA aniž by to aplikaci jakkoli ovlivnilo.
// B) STM8S103, 5V, 16MHz, vypneme nepoužívané periferie - 4.58mA void main(void){ CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 16MHz pro CPU i Periferie // Vypneme clock všem nepoužívaným periferiím (ponecháme jen TIM4 - milis) CLK->PCKENR1 = CLK_PCKENR1_TIM4; CLK->PCKENR2 = 0; init_milis(); // rozběhnout milis (TIM4) GPIO_Init(GPIOD,GPIO_PIN_4,GPIO_MODE_OUT_PP_LOW_SLOW); // Výstup na LEDku while(1){ process_LED(); // obsluhuj jen LEDku } }
Ve třetí ukázce využijeme režim spánku. Do hlavní smyčky programu přidáme funkci wfi(). Jakmile na ji program zavolá, uspí se. Přerušení od timeru 4 probudí CPU a program bude pokračovat dál od místa kde usnul. To znamená, že kód v hlavní smyčce se vykoná vždy jen jednou po každém přerušení do timeru 4 tedy každou milisekundu. Vzhledem k tomu, že náš program reaguje jen na hodnotu milis a ta se mění právě jen při přerušení timeru 4 tak nic víc nepotřebujeme. Čip se probudí, v rutině přerušení změní hodnotu milis, pak skočí do funkce process_LED() kde zkontroluje zda už uplynul potřebný čas pro změnu stavu LEDky, případně nastaví LEDku a pak zase usne. Volat funkci process_LED() stále dokola nemá význam neboť nic smysluplného do další změny milis udělat nemůže. Spotřeba klesne na 1.16mA (tedy o 78% proti prvnímu příkladu).
// C) STM8S103, 5V, 16MHz, vypneme nepoužívané periferie, uspáváme jádro režimem Wait - 1.16mA void main(void){ CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 16MHz pro CPU i Periferie // Vypneme clock všem nepoužívaným periferiím (ponecháme jen TIM4 - milis) CLK->PCKENR1 = CLK_PCKENR1_TIM4; CLK->PCKENR2 = 0; init_milis(); // rozběhnout milis (TIM4) GPIO_Init(GPIOD,GPIO_PIN_4,GPIO_MODE_OUT_PP_LOW_SLOW); // Výstup na LEDku while(1){ process_LED(); // obsluhuj jen LEDku wfi(); // spi než tě vzbudí jakékoli přerušení (zde TIM4 aka milis) } }
V poslední ukázce ještě snížíme taktovací frekvenci na 2MHz (nejméně co umožňuje dělička interního oscilátoru). Při tom nesmíme zapomenout změnit makro F_CPU na hodnotu 2MHz (Project->Settings->C_Compiler->Preprocessor_Definitions). Jinak se funkce milis inicializuje chybně a bude volat přerušení 8x pomaleji (tedy každých 8ms) a naše aplikace bude blikat ne každých 5s ale každých 40s. Spotřeba klesne na 0.77mA (od 85%) při 5V napájení a při 3.3V pak na 0.56mA. Pořád aniž by to mělo jakýkoli vliv na chování aplikace nebo aniž by to nějak komplikovalo program.
// D) STM8S103, 5V, 16MHz, vypneme nepoužívané periferie, uspáváme jádro režimem Wait, taktujeme 2MHz - 0.77mA void main(void){ CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8); // 2MHz pro CPU i Periferie // Vypneme clock všem nepoužívaným periferiím (ponecháme jen TIM4 - milis) CLK->PCKENR1 = CLK_PCKENR1_TIM4; CLK->PCKENR2 = 0; init_milis(); // rozběhnout milis (TIM4) GPIO_Init(GPIOD,GPIO_PIN_4,GPIO_MODE_OUT_PP_LOW_SLOW); // Výstup na LEDku while(1){ process_LED(); // obsluhuj jen LEDku wfi(); // spi než tě vzbudí jakékoli přerušení (zde TIM4 aka milis) } }
Celou naši demonstrační aplikaci bychom mohli napsat (a napíšem) ještě mnohem lépe s využitím režimu Halt a dostat spotřebu do oblasti nižších desítek uA. Než to ale uděláme, musím se seznámit s AWU (Auto Wakeup Unit). Obecně není STM8S žádný úsporný mikrokontrolér (na to jsou třeba STM8L). Moderní MCU stejnou aplikaci zvládají s odběrem okolo 1uA.
Home
V1.00 17.9.2023
By Michal Dudka (m.dudka@seznam.cz)