323 lines
11 KiB
C
323 lines
11 KiB
C
/* USER CODE BEGIN Header */
|
|
/**
|
|
******************************************************************************
|
|
* @file freertos_port.c
|
|
* @author MCD Application Team
|
|
* @brief Custom porting of FreeRTOS functionalities
|
|
******************************************************************************
|
|
* @attention
|
|
*
|
|
* Copyright (c) 2026 STMicroelectronics.
|
|
* All rights reserved.
|
|
*
|
|
* This software is licensed under terms that can be found in the LICENSE file
|
|
* in the root directory of this software component.
|
|
* If no LICENSE file comes with this software, it is provided AS-IS.
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
/* USER CODE END Header */
|
|
|
|
/* Includes ------------------------------------------------------------------*/
|
|
#include "app_common.h"
|
|
|
|
#include "FreeRTOS.h"
|
|
#include "task.h"
|
|
#include "stm32_lpm.h"
|
|
#include <limits.h>
|
|
|
|
/* Private typedef -----------------------------------------------------------*/
|
|
typedef struct
|
|
{
|
|
uint32_t LpTimeLeftOnEntry;
|
|
uint8_t LpTimerFreeRTOS_Id;
|
|
} LpTimerContext_t;
|
|
|
|
/* Private defines -----------------------------------------------------------*/
|
|
#ifndef configSYSTICK_CLOCK_HZ
|
|
#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ
|
|
/* Ensure the SysTick is clocked at the same frequency as the core. */
|
|
#define portNVIC_SYSTICK_CLK_BIT ( 1UL << 2UL )
|
|
#else
|
|
/* The way the SysTick is clocked is not modified in case it is not the same
|
|
as the core. */
|
|
#define portNVIC_SYSTICK_CLK_BIT ( 0 )
|
|
#endif
|
|
|
|
#define CPU_CLOCK_KHZ ( configCPU_CLOCK_HZ / 1000 )
|
|
|
|
/* Constants required to manipulate the core. Registers first... */
|
|
#define portNVIC_SYSTICK_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000e010 ) )
|
|
#define portNVIC_SYSTICK_LOAD_REG ( * ( ( volatile uint32_t * ) 0xe000e014 ) )
|
|
#define portNVIC_SYSTICK_CURRENT_VALUE_REG ( * ( ( volatile uint32_t * ) 0xe000e018 ) )
|
|
#define portNVIC_SYSTICK_INT_BIT ( 1UL << 1UL )
|
|
#define portNVIC_SYSTICK_ENABLE_BIT ( 1UL << 0UL )
|
|
#define portNVIC_SYSTICK_COUNT_FLAG_BIT ( 1UL << 16UL )
|
|
|
|
/* Private macros ------------------------------------------------------------*/
|
|
/* Private variables ---------------------------------------------------------*/
|
|
/*
|
|
* The number of SysTick increments that make up one tick period.
|
|
*/
|
|
#if ( CFG_LPM_SUPPORTED != 0)
|
|
static uint32_t ulTimerCountsForOneTick;
|
|
|
|
static LpTimerContext_t LpTimerContext;
|
|
#endif
|
|
/* Global variables ----------------------------------------------------------*/
|
|
/* Private function prototypes -----------------------------------------------*/
|
|
#if ( CFG_LPM_SUPPORTED != 0)
|
|
static void LpTimerInit( void );
|
|
static void LpTimerCb( void );
|
|
static void LpTimerStart( uint32_t time_to_sleep );
|
|
static void LpEnter( void );
|
|
static uint32_t LpGetElapsedTime( void );
|
|
void vPortSetupTimerInterrupt( void );
|
|
#endif
|
|
|
|
/* Functions Definition ------------------------------------------------------*/
|
|
|
|
/**
|
|
* @brief Implement the tickless feature
|
|
*
|
|
*
|
|
* @param: xExpectedIdleTime is given in number of FreeRTOS Ticks
|
|
* @retval: None
|
|
*/
|
|
void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
|
|
{
|
|
/* If low power is not used, do not stop the SysTick and continue execution */
|
|
#if ( CFG_LPM_SUPPORTED != 0)
|
|
/**
|
|
* Although this is not documented as such, when xExpectedIdleTime = 0xFFFFFFFF = (~0),
|
|
* it likely means the system may enter low power for ever ( from a FreeRTOS point of view ).
|
|
* Otherwise, for a FreeRTOS tick set to 1ms, that would mean it is requested to wakeup in 8 years from now.
|
|
* When the system may enter low power mode for ever, FreeRTOS is not really interested to maintain a
|
|
* systick count and when the system exits from low power mode, there is no need to update the count with
|
|
* the time spent in low power mode
|
|
*/
|
|
uint32_t ulCompleteTickPeriods;
|
|
|
|
/* Stop the SysTick to avoid the interrupt to occur while in the critical section.
|
|
* Otherwise, this will prevent the device to enter low power mode
|
|
* At this time, an update of the systick will not be considered
|
|
*
|
|
*/
|
|
portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;
|
|
|
|
/* Enter a critical section but don't use the taskENTER_CRITICAL()
|
|
method as that will mask interrupts that should exit sleep mode. */
|
|
__disable_irq();
|
|
__DSB();
|
|
__ISB();
|
|
|
|
/* 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 SysTick. */
|
|
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
|
|
|
|
/* Re-enable interrupts - see comments above __disable_interrupt()
|
|
call above. */
|
|
__enable_irq();
|
|
}
|
|
else
|
|
{
|
|
if (xExpectedIdleTime != (~0))
|
|
{
|
|
/* Remove one tick to wake up before the event occurs */
|
|
xExpectedIdleTime--;
|
|
/* Start the low power timer */
|
|
LpTimerStart( xExpectedIdleTime );
|
|
}
|
|
|
|
/* Enter low power mode */
|
|
LpEnter( );
|
|
|
|
if (xExpectedIdleTime != (~0))
|
|
{
|
|
/**
|
|
* Get the number of FreeRTOS ticks that has been suppressed
|
|
* In the current implementation, this shall be kept in critical section
|
|
* so that the timer server return the correct elapsed time
|
|
*/
|
|
ulCompleteTickPeriods = LpGetElapsedTime( );
|
|
vTaskStepTick( ulCompleteTickPeriods );
|
|
}
|
|
|
|
/* Restart SysTick */
|
|
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
|
|
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
|
|
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
|
|
|
|
/* Exit with interrUpts enabled. */
|
|
__enable_irq();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Setup the systick timer to generate the tick interrupts at the required
|
|
* frequency and initialize a low power timer
|
|
* The current implementation is kept as close as possible to the default tickless
|
|
* mode provided.
|
|
* The systick is still used when there is no need to go in low power mode.
|
|
* When the system needs to enter low power mode, the tick is suppressed and a low power timer
|
|
* is used over that time
|
|
* Note that in sleep mode, the system clock is still running and the default tickless implementation
|
|
* using systick could have been kept.
|
|
* However, as at that time, it is not yet known whereas the low power mode that will be used is stop mode or
|
|
* sleep mode, it is easier and simpler to go with a low power timer as soon as the tick need to be
|
|
* suppressed.
|
|
*/
|
|
#if ( CFG_LPM_SUPPORTED != 0)
|
|
void vPortSetupTimerInterrupt( void )
|
|
{
|
|
LpTimerInit( );
|
|
|
|
/* Calculate the constants required to configure the tick interrupt. */
|
|
ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
|
|
|
|
/* Stop and clear the SysTick. */
|
|
portNVIC_SYSTICK_CTRL_REG = 0UL;
|
|
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
|
|
|
|
/* Configure SysTick to interrupt at the requested rate. */
|
|
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
|
|
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief The current implementation uses the hw_timerserver to provide a low power timer
|
|
* This may be replaced by another low power timer.
|
|
*
|
|
* @param None
|
|
* @retval None
|
|
*/
|
|
#if ( CFG_LPM_SUPPORTED != 0)
|
|
static void LpTimerInit( void )
|
|
{
|
|
( void ) HW_TS_Create(CFG_TIM_PROC_ID_ISR, &(LpTimerContext.LpTimerFreeRTOS_Id), hw_ts_SingleShot, LpTimerCb);
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief Low power timer callback
|
|
*
|
|
* @param None
|
|
* @retval None
|
|
*/
|
|
#if ( CFG_LPM_SUPPORTED != 0)
|
|
static void LpTimerCb( void )
|
|
{
|
|
/**
|
|
* Nothing to be done
|
|
*/
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief Request to start a low power timer ( running is stop mode )
|
|
*
|
|
* @param time_to_sleep : Number of FreeRTOS ticks
|
|
* @retval None
|
|
*/
|
|
#if ( CFG_LPM_SUPPORTED != 0)
|
|
static void LpTimerStart( uint32_t time_to_sleep )
|
|
{
|
|
uint64_t time;
|
|
|
|
/* Converts the number of FreeRTOS ticks into hw timer tick */
|
|
if (time_to_sleep > (ULLONG_MAX / 1e12)) /* Prevent overflow in else statement */
|
|
{
|
|
time = 0xFFFF0000; /* Maximum value equal to 24 days */
|
|
}
|
|
else
|
|
{
|
|
/* The result always fits in uint32_t and is always less than 0xFFFF0000 */
|
|
time = time_to_sleep * 1000000000000ULL;
|
|
time = (uint64_t)( time / ( CFG_TS_TICK_VAL_PS * configTICK_RATE_HZ ));
|
|
}
|
|
|
|
HW_TS_Start(LpTimerContext.LpTimerFreeRTOS_Id, (uint32_t)time);
|
|
|
|
/**
|
|
* There might be other timers already running in the timer server that may elapse
|
|
* before this one.
|
|
* Store how long before the next event so that on wakeup, it will be possible to calculate
|
|
* how long the tick has been suppressed
|
|
*/
|
|
LpTimerContext.LpTimeLeftOnEntry = HW_TS_RTC_ReadLeftTicksToCount( );
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief Enter low power mode
|
|
*
|
|
* @param None
|
|
* @retval None
|
|
*/
|
|
#if ( CFG_LPM_SUPPORTED != 0)
|
|
static void LpEnter( void )
|
|
{
|
|
#if ( CFG_LPM_SUPPORTED == 1)
|
|
UTIL_LPM_EnterLowPower();
|
|
#endif
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief Read how long the tick has been suppressed
|
|
*
|
|
* @param None
|
|
* @retval The number of tick rate (FreeRTOS tick)
|
|
*/
|
|
#if ( CFG_LPM_SUPPORTED != 0)
|
|
static uint32_t LpGetElapsedTime( void )
|
|
{
|
|
uint64_t val_ticks, time_ps;
|
|
uint32_t LpTimeLeftOnExit;
|
|
|
|
LpTimeLeftOnExit = HW_TS_RTC_ReadLeftTicksToCount();
|
|
/* This cannot overflow. Max result is ~ 1.6e13 */
|
|
time_ps = (uint64_t)((CFG_TS_TICK_VAL_PS) * (uint64_t)(LpTimerContext.LpTimeLeftOnEntry - LpTimeLeftOnExit));
|
|
|
|
/* time_ps can be less than 1 RTOS tick in following situations
|
|
* a) MCU didn't go to STOP2 due to wake-up unrelated to Timer Server or woke up from STOP2 very shortly after.
|
|
* Advancing RTOS clock by 1 FreeRTOS tick doesn't hurt in this case.
|
|
* b) vPortSuppressTicksAndSleep(xExpectedIdleTime) was called with xExpectedIdleTime = 2 which is minimum value defined by configEXPECTED_IDLE_TIME_BEFORE_SLEEP.
|
|
* The xExpectedIdleTime is decremented by one RTOS tick to wake-up in advance.
|
|
* Ex: RTOS tick is 1ms, the timer Server wakes the MCU in ~977 us. RTOS clock should be advanced by 1 ms.
|
|
* */
|
|
if(time_ps <= (1e12 / configTICK_RATE_HZ)) /* time_ps < RTOS tick */
|
|
{
|
|
val_ticks = 1;
|
|
}
|
|
else
|
|
{
|
|
/* Convert pS time into OS ticks */
|
|
val_ticks = time_ps * configTICK_RATE_HZ; /* This cannot overflow. Max result is ~ 1.6e16 */
|
|
val_ticks = (uint64_t)(val_ticks / (1e12)); /* The result always fits in uint32_t */
|
|
}
|
|
|
|
/**
|
|
* The system may have been out from another reason than the timer
|
|
* Stop the timer after the elapsed time is calculated other wise, HW_TS_RTC_ReadLeftTicksToCount()
|
|
* may return 0xFFFF ( TIMER LIST EMPTY )
|
|
* It does not hurt stopping a timer that exists but is not running.
|
|
*/
|
|
HW_TS_Stop(LpTimerContext.LpTimerFreeRTOS_Id);
|
|
|
|
return (uint32_t)val_ticks;
|
|
}
|
|
#endif
|