2012-03-25 06:39 AM
Hi all,
I am using the STM32F103CB microcontroller to develop an embedded application for wireless communications that needs to sleep and wake up periodically in order to save energy, as it runs from batteries. I have already developed the drivers to manage the GPIO, EXTI, SPI, etc. and right now I am trying to get the low power stuff to work using the STOP mode. What I do is enable the LSE clock (32kHz clock) to drive the RTC unit following the examples available. After that I put the microcontroller to STOP mode using the PWR_EnterSTOPMode instruction. Then, when the counter associated to the RTC unit overflows an interruption (RTC_IT_OW) should be fired. I have configured the NVIC_IRQChannel to handle the RTC_IRQn interrupt and have written the code to manage the interrupt. After the interruption is handled the code should keep running from where it stopped (the PWR_EnterSTOPMode instruction). I have set up an example as described and tried it using a logic analyzer using the GPIOs as debug ports. From what I see in the logic analyzer, the stop mode is entered and exited when the interrupt is produced but this happens only once, so that is weird. Could you please provide your insights? Thanks in advance. The code is as follows. Here's the code to init the RTC clock:void stm32f10x_rtc_init(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); // Enable PWR/BKP
PWR_BackupAccessCmd(ENABLE); // Allow access to BKP Domain
BKP_DeInit(); // Reset Backup Domain
RCC_LSEConfig(RCC_LSE_ON); // Enable LSE
while ((RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET))
// Wait till LSE is ready
;
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); // Select LSE as RTC Clock Source
RCC_RTCCLKCmd(ENABLE); // Enable RTC Clock
RTC_WaitForSynchro(); // Wait for RTC registers synchronization
RTC_WaitForLastTask(); // Wait until last write operation on RTC registers has finished
RTC_ClearITPendingBit(RTC_IT_OW);
RTC_ITConfig(RTC_IT_OW, ENABLE); // Enables overflow interrupt
RTC_WaitForLastTask(); // Wait until last write operation on RTC registers has finished
RTC_SetPrescaler(1000); // Set RTC prescaler period to 10ms
RTC_WaitForLastTask(); // Wait until last write operation on RTC registers has finished
NVIC_InitTypeDef NVIC_InitStructure; // Configure the interrupt handler
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
Here's the code to put the microcontroller to STOP mode:
void stm32f10x_rtc_stop(void) { // Puts the microcontroller to stop mode
stm32f10x_gpio_toggle(debug2_p, DISABLE);
PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI); // stm32f10x_pwr.c
stm32f10x_gpio_toggle(debug2_p, ENABLE);
stm32f10x_rtc_sysclk_conf();
}
Here's the code to get the clocks back to work:
void stm32f10x_rtc_sysclk_conf(void) {
ErrorStatus HSEStartUpStatus;
/* Enable HSE */
RCC_HSEConfig(RCC_HSE_ON);
/* Wait till HSE is ready */
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if (HSEStartUpStatus == SUCCESS) {
/* Enable PLL */
RCC_PLLCmd(ENABLE);
/* Wait till PLL is ready */
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {
}
/* Select PLL as system clock source */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* Wait till PLL is used as system clock source */
while (RCC_GetSYSCLKSource() != 0x08) {
}
}
}
Finally, here's the code to handle the interrupt:
volatile uint8_t rtc_status = 0;
void RTC_IRQHandler(void) { // @todo RTC interrupt handler
if (RTC_GetITStatus(RTC_IT_OW) != RESET) {
rtc_status = !rtc_status;
stm32f10x_gpio_toggle(debug1_p, rtc_status);
RTC_WaitForLastTask();
RTC_ClearITPendingBit(RTC_IT_OW);
RTC_WaitForLastTask();
}
}
Last but not least, here's the main code:
int main(void) {
stm32f10x_init(); // Inits GPIO, EXTI, etc.
stm32f10x_rtc_init(); // Inits the microcontroller RTC timer
while (1) {
stm32f10x_rtc_stop(); // Puts the microcontroller to stop mode
}
}
What am I overlooking? Any ideas? Thanks in advance!
Regards,
Pere
#stm32-rtc-stop
2012-03-25 03:25 PM
How often do you expect the counter to overflow? Once every 136 Years?
What's the deal with the 1000 prescaler? I don't see the correlation between this, 32KHz and 10 ms, explain.2012-03-25 04:44 PM
First of all, thank you for your reply. Secondly, sorry because I was kind of confused too; it is the first time I work with STOP mode in STM32F1 microcontrollers. Now that I have checked the user manual again some things are clearer, though the application still does not work properly. Let's go step by step.
As far as I understand if the RTC clock runs at 32768 Hz and if I prescale it by 1 I get one interrupt each 1/32768 seconds, right? Then, if I want to get one interrupt each 10 ms I should prescale it by 1 and have a counter (RTC_CNT) count to 328 (aprox.), right? Thus, if the RTC_CNT register is 32 bits and can count up to 0xFFFFFFFF I should set the counter value to 0xFFFFFFFF - 0x148 (328) = 0xFFFFFEB7 in order for the counter to overflow each 10 ms, right? Well, I tried that with the code showed next and it did not work as expected. It seems to go to STOP but it does not wake up a single time. Any ideas? Thanks again!void stm32f10x_rtc_init(void) {
NVIC_InitTypeDef NVIC_InitStructure; // Configure the interrupt handler
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); // Enable PWR/BKP
PWR_BackupAccessCmd(ENABLE); // Allow access to BKP Domain
BKP_DeInit(); // Reset Backup Domain
RCC_LSEConfig(RCC_LSE_ON); // Enable LSE
while ((RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)) ;
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); // Select LSE as RTC Clock Source
RCC_RTCCLKCmd(ENABLE); // Enable RTC Clock
RTC_WaitForSynchro(); // Wait for RTC registers synchronization
RTC_WaitForLastTask(); // Wait until last write operation on RTC registers has finished
RTC_ClearITPendingBit(RTC_IT_OW);
RTC_ITConfig(RTC_IT_OW, ENABLE); // Enables overflow interrupt
RTC_WaitForLastTask(); // Wait until last write operation on RTC registers has finished
RTC_SetPrescaler(1); // Set RTC prescaler
RTC_WaitForLastTask(); // Wait until last write operation on RTC registers has finished
}
void stm32f10x_rtc_stop(void) {
RTC_WaitForLastTask();
RTC_SetCounter(0xFFFFFEB7);
RTC_WaitForLastTask();
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
stm32f10x_rcc_init();
}
void stm32f10x_rcc_init(void) {
ErrorStatus HSEStartUpStatus;
/* RCC system reset(for debug purpose) */
RCC_DeInit();
/* Enable HSE */
RCC_HSEConfig(RCC_HSE_ON);
/* Wait till HSE is ready */
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if (HSEStartUpStatus == SUCCESS) {
/* HCLK = SYSCLK */
RCC_HCLKConfig(RCC_SYSCLK_Div1);
/* PCLK2 = HCLK */
RCC_PCLK2Config(RCC_HCLK_Div1);
/* PCLK1 = HCLK/2 */
RCC_PCLK1Config(RCC_HCLK_Div2);
/* PLLCLK = 8MHz * 9 = 72 MHz */
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
/* Enable PLL */
RCC_PLLCmd(ENABLE);
/* Wait till PLL is ready */
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
;
/* Select PLL as system clock source */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* Wait till PLL is used as system clock source */
while (RCC_GetSYSCLKSource() != 0x08)
;
}
}
2012-03-25 07:24 PM
I think you want to try SLEEP. The RTC wrap is not a defined exit for STOP, which to the best of my recollection wants an EXTI source.
The RTC is best used for 1 second timing, or fractional seconds. I'm not sure how low you can set the prescale, or the domain synchronization delays (perhaps 60us), but for 10ms you're going to be doing a lot of work, restarting things, and waiting for clocks to start, PLLs to lock and spin loops. I would generally code my RCC stuff more carefully, paying particular attention to what clocks are currently selected, running, etc. This is also important in your reset path if you use STANDBY, and the RTC alarm.2012-03-25 10:41 PM
Well, I have tried the same code with a prescaler of 32767 to obtain a 1 Hz interrupt and the counter set to 0xFFFFFFFE so that it overflows each second, but it still does not work. It seems there is something wrong either with mycode or with the microcontroller, because the user manual states that the overflow of the RTC counter should cause an interrupt and wake up from STOP. Should I use the alarm interrupt instead?
Regarding your proposal to use SLEEP, can I reduce the consumption to ~uA? My application needs to last for several years using batteries and the only way to go is having a consumption in that order of magnitude. As I have seen in the microcontroller datasheet I could use the HSI clock at 125 kHz before entering the sleep mode and restoring the HSE clock right after coming out of sleep mode. With that approach the consumption would be around 500 uA. What do you think?