The STM8L152 is described as “8-bit ultra-low-power MCU” with “5 low-power modes: Wait, Low-power run (5.9 μA), Low-power wait (3 μA), Active halt with full RTC (1.4 μA), Halt (400 nA)“.
Usage of the Low Power Wait mode basically means to issue the “wfe” or “wfi” machine instructions, which can be done with inline assembler in SDCC:
__asm__("wfe\n"); //Wait for Event
__asm__("wfi\n"); //Wait for Interrupt
When one of these instructions is executed, the processor is suspended and the code execution is stopped until an interrupt is requested and the interrupt is serviced (wfi) or an event terminates the wait state without calling an interrupt service routine (wfe).
The wait instructions can be used during normal operation of the µC, which result in two “wait-Modes”. When additional measures are taken, such as turning off the FLASH memory and running wait instructions in RAM, then with the “low power wait-Modes” 3µA current consumption can be achieved.
The following example uses wfe to wait for an event (“Wake-Up-Event”) emitted by the Real Time Clock (RTC). To use this feature of the RTC, the WUTR divider must be programmed. As on the STM8L152_nucleo there is a 38kHz RC as Low Speed Internal clock source, a wakeup-period of one second is achieved by programming the value 38000/8 and setting the WUKSEL field in CR1 to 1, which means that the wake-up timer is supplied with 38kHz divided by 8. The RTC prescaler is not set, which leads to an RTCCLK of (approximately!) 38kHz.
The following code shows the routines to initialize the Low-Power Wait for Event mode (STM8L15x_Powersave.c):
#include "STM8L15x_Powersave.h"
extern CLK_t *CLK;
extern RTC_t *RTC;
extern FLASH_t *FLASH;
extern WFE_t *WFE;
extern char* switch_to_low_power_mode;
volatile uint16_t RAMFUN_LEN;
inline void get_ram_section_length() {
__asm
pushw x
ldw x, #l_RAMFUN
ldw _RAMFUN_LEN, x
popw x
__endasm;
}
uint8_t enable_rtc(char *buf)
{
char *src, *dst;
//Get length of RAMFUN segment
get_ram_section_length();
//Copy RAMFUN Segment to RAM
src=(char*)&switch_to_low_power_mode;
dst=(char*)buf;
for(uint16_t i=0; i<RAMFUN_LEN; i++)
{
*dst++ = *src++;
}
//Supply CLK to RTC
CLK->PCKENR2 |= (1<<2);
//Enable LSI
CLK->ICKCR |= (1<<2);
while((CLK->ICKCR & (1<<3)) == 0);
// LSI als RTC-Taktquelle auswählen
CLK->CRTCR = (1 << 2);
//Generate Event from RTC Int
WFE->CR4 |= 1;
//Unlock RTC Regs
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
//Set Init Mode
RTC->ISR1 |= (1<<7);
while(!(RTC->ISR1 & (1<<6)));
RTC->CR1 = 0;
RTC->CR2 = 0;
RTC->CR3 = 0;
//Set Prescaler (LSI=38kHz)
uint16_t prescaler = 38000;
RTC->SPRERH = (prescaler & 0xFF00)>>8;
RTC->SPRERL = (prescaler & 0x00FF)>>0;
for(volatile uint32_t i=0;i < 5000; i++);
//Disable WakeupTimer
do
{
RTC->CR2 &= ~(1<<2/*Wakeup Timer Disable*/);
}
while((RTC->ISR1 & (1<<2)) == 0)/*i.e. while WUT is not writable*/;
//Program Wake-Up Time (here: 38/2 k->1s)
prescaler = 38000/8;
RTC->WUTRH = (prescaler & 0xFF00)>>8;
RTC->WUTRL = (prescaler & 0x00FF)>>0;
//WUKSEL (CR1) == 0 -> f_RTC/8
RTC->CR1 &= 0x07;
RTC->CR1 |= 0x01;
//Enable Wakeup-Counter
RTC->CR2 |= (1<<2);
//Enable Interrupt
RTC->CR2 |= (1<<6);
//Release Init Mode
RTC->ISR1 &= ~(1<<7);
RTC->WPR = 0xFF;
return 1;
}
Since the wfe-instruction must be executed from RAM (due to smaller current consumption there), the implementation of the routine, with which the µC is switched into low power mode is done in a seperate file. This is where the magic happens. (RamFun.c):
#include "RamFun.h"
extern int ramcnt;
extern CLK_t *CLK;
extern RTC_t *RTC;
extern FLASH_t *FLASH;
void switch_to_low_power_mode(void)
{
/*
1. Jump to RAM
2. Switch system clock to LSI or LSE clock sources
3. Switch off the high speed oscillators, the ADC and all unused peripherals
4. Mask all interrupts
5. Switch off the Flash/Data EEPROM by setting EEPM bit in FLASH_CR1 registe
6. Add a software delay loop to ensure Flash/Data EEPROM off status
7. Configure the ultra-low-power mode for the regulator by setting the REGOF
*/
/* 1.) */
//Here we are!
//Optional: test, wether we really are in RAM, for debugging...
/* 2.) */
//Switch To LSI
CLK->SWR = 0x02;
while(CLK->SWR &1);
/* 3.) */
CLK->ICKCR &= ~1;
/* 4.) */
sim();
/* 5.) */
FLASH->CR1 |= (1<<3);
/* 6.) */
for(volatile uint16_t i = 0; i< 380; i++);
/* 7.) */
CLK->REGCSR |= (1<<1);
wfe();
RTC->ISR2 &= ~(1<<2);
/*
1.Switch on the main regulator by resetting the REGOFF bit in the CLK_REGCSR
2.Switch on the Flash/Data EEPROM by resetting EEPM bit in FLASH_CR1 registe
3.Reset interrupt mask.
4.Switch on what is necessary and jump to Flash/Data EEPROM if needed.
*/
/* 1.) */
CLK->REGCSR &= ~(1<<1);
while((CLK->REGCSR & 1) == 0);
/* 2.) */
FLASH->CR1 &= ~(1<<3);
/* 2.) */
rim();
/* 4.) */
//Enabling HSI is not explicitly neccessary
//Switch To HSI
CLK->SWR = 0x01;
while(CLK->SWR &1);
}