AnsweredAssumed Answered

STM32L152RC RTC for freeRTOS Tickless mode

Question asked by Tim on Oct 1, 2014
Latest reply on Oct 1, 2014 by Tim
Starting position: 
       
  • STM32L1 Discovery Kit with STM32L152RCT6
  •    
  • FreeRTOS V8.1.2
  •    
  • System tick within FreeRTOS is internal RTC configured as wakeup interrupt
FreeRTOS tickless mode:

The FreeRTOS tickless idle mode stops the periodic tick interrupt during idle periods (periods when there are no application tasks that are able to execute), then makes a correcting adjustment to the RTOS tick count value when the tick interrupt is restarted.

Stopping the tick interrupt allows the microcontroller to remain in a deep power saving state until either an interrupt occurs, or it is time for the RTOS kernel to transition a task into the Ready state.

Waking up from low power mode (in our case stop mode) is handled either by the rtc wakeup interrupt or an incoming external interrupt (EXTI).

(further information: FreeRTOS tickless mode)

Activity of the application:

The application shall handle two main tasks:
       
  • Low priority task which sets a LED every second for an amount of time (let's say 50ms)
  •    
  • Handle an external interrupt (example button pressed)
During inactive time the controller is put into stop mode. Thus the controller wakes up from stop mode either by pressing the button or by a rtc wakeup interrupt.

Waking up from stop mode by rtc wakeup interrupt or external interrupt is done within the FreeRTOS macro portSUPPRESS_TICKS_AND_SLEEP(). Within the application we have overridden the default implementation by defining portSUPPRESS_TICKS_AND_SLEEP() in FreeRTOSConfig.h.

Problem:

Waking up from rtc or external interrupt works fine. After waking up from an external interrupt (in our example a button press), we have to get the amount of time we have been in low power mode. This is needed for updating the number of ticks already elapsed within the FreeRTOS kernel. Thought this would be possible by calculating the difference between RTC reload value and the current value within RTC WUTR register. But the WUTR register always holds the reload value. Following the code section where the elapsed time should be calculated:

/* Stop tick source.  Again, the time the tick is stopped for is
accounted for as best it can be, but using the tickless mode will
inevitably result in some tiny drift of the time maintained by the
kernel with respect to calendar time. */
pmDISABLE_RTC_WRITE_PROTECTION();
{
    pmDISABLE_RTC();
}
pmENABLE_RTC_WRITE_PROTECTION();
  
if( ( RTC->ISR & RTC_FLAG_WUTF ) != 0 )
{
    /* The tick interrupt handler will already have pended the tick
    processing in the kernel. As the pending tick will be processed
    as soon as this function exits, the tick value maintained by
    the tick is stepped forward by one less than the time spent
    waiting. */
    ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
}
else
{
    /* Something other than the tick interrupt ended the sleep.
    Work out how long the sleep lasted rounded to complete tick
    periods (not the ulReload value which accounted for part
    ticks). */
    u32SleepTime = ((uint32_t)(RTC->WUTR & RTC_WUTR_WUT));
    ulCompletedSysTickDecrements = ( ulReloadValue + 1UL ) - u32SleepTime;
    /* How many complete tick periods passed while the processor
    was waiting? */
    ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;
}

See at the end of this post the full code for the function vPMSuppressTicksAndSleep (called within the macro portSUPPRESS_TICKS_AND_SLEEP()).

Question:

       
  • Is there any bug within the code?
  •    
  • Is the register WUTR not updated by the rtc?
  •    
  • Is there an other way to calculate the ellapsed time after waking up by an external interrupt?
Code:

vPMSuppressTicksAndSleep --> called within the macro portSUPPRESS_TICKS_AND_SLEEP()
void vPMSuppressTicksAndSleep( portTickType xExpectedIdleTime )
{
unsigned long ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements;
portTickType xModifiableIdleTime;
unsigned portCHAR sleepMode;
uint32_t u32SleepTime = 0;
  
    sleepMode =  ucPMGetSleepMode();
    if( ( sleepMode == RUN ) || ( sleepMode == LOW_POWER_RUN ) )
    {
        /* In run mode we do not want to go to sleep. */
        return;
    }
  
    /* Make sure the tick reload value does not overflow the counter. */
    if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
    {
        xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
    }
  
    /* Stop the tick source momentarily.  The time the tick is stopped for
    is accounted for as best it can be, but using the tickless mode will
    inevitably result in some tiny drift of the time maintained by the
    kernel with respect to calendar time. */
    pmDISABLE_RTC_WRITE_PROTECTION();
    {
        pmDISABLE_RTC();
    }
    pmENABLE_RTC_WRITE_PROTECTION();
  
    /* Calculate the reload value required to wait xExpectedIdleTime tick
    periods. -1 is used because this code will execute part way through one
    of the tick periods. */
    ulReloadValue = RTC->WUTR + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
    if( ulReloadValue > ulStoppedTimerCompensation )
    {
        ulReloadValue -= ulStoppedTimerCompensation;
    }
  
    /* Enter a critical section but don't use the taskENTER_CRITICAL()
    method as that will mask interrupts that should exit sleep mode. */
    __asm volatile( "cpsid i" );
  
    /* If a context switch is pending or a task is waiting for the scheduler
    to be unsuspended then abandon the low power entry. */
    if( eTaskConfirmSleepModeStatus() == eAbortSleep )
    {
        /* Restart tick source. */
        pmDISABLE_RTC_WRITE_PROTECTION();
        {
            RTC->CR |= RTC_CR_WUTE;
        }
        pmENABLE_RTC_WRITE_PROTECTION();
  
        /* Re-enable interrupts - see comments above the cpsid instruction
        above. */
        __asm volatile( "cpsie i" );
    }
    else
    {
        pmDISABLE_RTC_WRITE_PROTECTION();
        {
            /* Wait until WUT register is writable. */
            pmWAIT_WUT_REG();
  
            /* Set the new reload value. */
            RTC->WUTR = ulReloadValue;
  
            /* Clear the interrupt pending bits in the RTC_ISR register */
            RTC->ISR &= ~RTC_FLAG_WUTF;
  
            /* Restart tick source. */
            RTC->CR |= RTC_CR_WUTE;
        }
        pmENABLE_RTC_WRITE_PROTECTION();
  
        /* Sleep until something happens. configPRE_SLEEP_PROCESSING() can
        set its parameter to 0 to indicate that its implementation contains
        its own wait for interrupt or wait for event instruction, and so wfi
        should not be executed again. However, the original expected idle
        time variable must remain unmodified, so a copy is taken. */
        xModifiableIdleTime = xExpectedIdleTime;
        configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
        if( xModifiableIdleTime > 0 )
        {
            xPMGoToSleepMode( ucPMGetSleepMode() );
        }
        configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
  
        /* Stop tick source.  Again, the time the tick is stopped for is
        accounted for as best it can be, but using the tickless mode will
        inevitably result in some tiny drift of the time maintained by the
        kernel with respect to calendar time. */
        pmDISABLE_RTC_WRITE_PROTECTION();
        {
            pmDISABLE_RTC();
        }
        pmENABLE_RTC_WRITE_PROTECTION();
  
        if( ( RTC->ISR & RTC_FLAG_WUTF ) != 0 )
        {
            /* The tick interrupt handler will already have pended the tick
            processing in the kernel. As the pending tick will be processed
            as soon as this function exits, the tick value maintained by
            the tick is stepped forward by one less than the time spent
            waiting. */
            ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
        }
        else
        {
            /* Something other than the tick interrupt ended the sleep.
            Work out how long the sleep lasted rounded to complete tick
            periods (not the ulReload value which accounted for part
            ticks). */
            u32SleepTime = ((uint32_t)(RTC->WUTR & RTC_WUTR_WUT));
            ulCompletedSysTickDecrements = ( ulReloadValue + 1UL ) - u32SleepTime;
  
            /* How many complete tick periods passed while the processor
            was waiting? */
            ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;
        }
  
        /* Re-enable interrupts - see comments above the cpsid instruction
        above. */
        __asm volatile( "cpsie i" );
  
        /* Restart tick source with the old reload value again. The
        critical section is used to ensure the tick interrupt can only
        execute once in the case that the reload register is near zero. */
        portENTER_CRITICAL();
        {
            pmDISABLE_RTC_WRITE_PROTECTION();
            {
                /* Wait until WUT register is writable. */
                pmWAIT_WUT_REG();
                /* Reset tick counter value. */
                RTC->WUTR = ulTimerCountsForOneTick - 1UL;
  
                /* Restart tick source. */
                RTC->CR |= RTC_CR_WUTE;
            }
            pmENABLE_RTC_WRITE_PROTECTION();
  
            vTaskStepTick( ulCompleteTickPeriods );
        }
        portEXIT_CRITICAL();
  
        /* Reset ucFlagISR if SSM issues a request before idle task */
        ucFlagISR = 0;
    }
}

pmDISABLE_RTC_WRITE_PROTECTION
#define pmDISABLE_RTC_WRITE_PROTECTION() {  \
     RTC->WPR = 0xCA; \
     RTC->WPR = 0x53; \
}


pmENABLE_RTC_WRITE_PROTECTION
#define pmENABLE_RTC_WRITE_PROTECTION() { \
    RTC->WPR = 0xFF; \
}


pmWAIT_WUT_REG
#define pmWAIT_WUT_REG()   {   \
    uint32_t wutcounter = 0;    \
    /* Wait until we can write to the RTC registers. */ \
    while( ( ( RTC->ISR & RTC_ISR_WUTWF ) == 0 ) &&     \
           ( wutcounter != pmRTC_DISABLE_TIMEOUT ) )    \
    {                                                   \
        wutcounter++;                                   \
    }                                                   \
}


pmDISABLE_RTC
#define pmDISABLE_RTC()                                 \
{                                                       \
    RTC->CR &= ~RTC_CR_WUTE;                            \
    /* Wait until we can write to the RTC registers. */ \
    pmWAIT_WUT_REG();                                   \
}

Thanks for any help! 
Tim

Outcomes