From 000e04d76842a6e928f1086e6650dc4d3b3a8fb2 Mon Sep 17 00:00:00 2001 From: Vincent Coubard Date: Fri, 27 May 2016 09:33:37 +0100 Subject: [PATCH] RTOS port for nrf51. The NRF51 doesn't have a systick. When the MCU doesn't have a systick, the HAL has to export several functions which will be use by the kernel to manage the tick: * os_tick_init provides the initialization function for the alternative hardware timer. * os_tick_val returns the current value of the alternative hardware timer. * os_tick_ovf returns the overflow flag of the alternative hardware timer. * os_tick_irqack is an interrupt acknowledge function that is called to confirm the alternative hardware timer interrupt. The HAL should also call OS_Tick_Handler needs to be called as the hardware timer interrupt function. In the case of the NRF51, two RTCs are available: * RTC0: reserved for soft device * RTC1: used by us_ticker. RTC1 is a 4 channels timers, channel 0 is used for us_ticker, and in this port channel 1 is used for tick generation. Implementation notes: * RTC1_IRQHandler: has to be written in assembly otherwise a stack overflow will occur because the function OS_Tick_Handler never returns. This function is called when RTC1 channel IRQ is triggered. * tick generation has been optimised for a tick with a duration of 1000us. * us_ticker can still be compiled and used without RTX enabled. More information about alternative timer as RTX Kernel Timer: https://www.keil.com/pack/doc/CMSIS/RTX/html/_timer_tick.html --- .../TARGET_MCU_NRF51822/us_ticker.c | 355 +++++++++++++++++- rtos/rtx/TARGET_CORTEX_M/RTX_CM_lib.h | 7 +- rtos/rtx/TARGET_CORTEX_M/RTX_Conf_CM.c | 37 +- 3 files changed, 374 insertions(+), 25 deletions(-) diff --git a/hal/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/us_ticker.c b/hal/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/us_ticker.c index 78f966ab7f..06e879551b 100644 --- a/hal/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/us_ticker.c +++ b/hal/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/us_ticker.c @@ -55,6 +55,34 @@ static bool us_ticker_inited = false; static volatile uint32_t overflowCount; /**< The number of times the 24-bit RTC counter has overflowed. */ static volatile bool us_ticker_callbackPending = false; static uint32_t us_ticker_callbackTimestamp; +static bool os_tick_started = false; /**< flag indicating if the os_tick has started */ +/** + * The value previously set in the capture compare register of channel 1 + */ +static uint32_t previous_tick_cc_value = 0; + +/* + RTX provide the following definitions which are used by the tick code: + * os_trv: The number (minus 1) of clock cycle between two tick. + * os_clockrate: Time duration between two ticks (in us). + * OS_Tick_Handler: The function which handle a tick event. + This function is special because it never returns. + Those definitions are used by the code which handle the os tick. + To allow compilation of us_ticker programs without RTOS, those symbols are + exported from this module as weak ones. + */ +#if defined (__CC_ARM) /* ARMCC Compiler */ +__attribute__((weak)) uint32_t const os_trv; +__attribute__((weak)) uint32_t const os_clockrate; +__attribute__((weak)) void OS_Tick_Handler() { } +#elif defined (__GNUC__) /* GNU Compiler */ +__attribute__((weak)) uint32_t const os_trv = 31; +__attribute__((weak)) uint32_t const os_clockrate = 1000; +__attribute__((noreturn, naked, weak)) void OS_Tick_Handler() { } +#else +#error Compiler not supported. +#error Weak definitions of os_trv, os_clockrate and OS_Tick_Handler should be provided. +#endif static inline void rtc1_enableCompareInterrupt(void) { @@ -110,15 +138,22 @@ static void rtc1_start() */ void rtc1_stop(void) { - NVIC_DisableIRQ(RTC1_IRQn); - rtc1_disableCompareInterrupt(); - rtc1_disableOverflowInterrupt(); + // If the os tick has been started, RTC1 shouldn't be stopped + // In that case, us ticker and overflow interrupt are disabled. + if (os_tick_started) { + rtc1_disableCompareInterrupt(); + rtc1_disableOverflowInterrupt(); + } else { + NVIC_DisableIRQ(RTC1_IRQn); + rtc1_disableCompareInterrupt(); + rtc1_disableOverflowInterrupt(); - NRF_RTC1->TASKS_STOP = 1; - nrf_delay_us(MAX_RTC_TASKS_DELAY); + NRF_RTC1->TASKS_STOP = 1; + nrf_delay_us(MAX_RTC_TASKS_DELAY); - NRF_RTC1->TASKS_CLEAR = 1; - nrf_delay_us(MAX_RTC_TASKS_DELAY); + NRF_RTC1->TASKS_CLEAR = 1; + nrf_delay_us(MAX_RTC_TASKS_DELAY); + } } /** @@ -148,11 +183,11 @@ static inline uint32_t rtc1_getCounter(void) } /** - * @brief Function for handling the RTC1 interrupt. + * @brief Function for handling the RTC1 interrupt for us ticker (capture compare channel 0 and overflow). * * @details Checks for timeouts, and executes timeout handlers for expired timers. */ -void RTC1_IRQHandler(void) +void us_ticker_handler(void) { if (NRF_RTC1->EVENTS_OVRFLW) { overflowCount++; @@ -271,3 +306,305 @@ void us_ticker_clear_interrupt(void) NRF_RTC1->EVENTS_OVRFLW = 0; NRF_RTC1->EVENTS_COMPARE[0] = 0; } + + +#if defined (__CC_ARM) /* ARMCC Compiler */ + +__asm void RTC1_IRQHandler(void) +{ + IMPORT OS_Tick_Handler + IMPORT us_ticker_handler + + /** + * Chanel 1 of RTC1 is used by RTX as a systick. + * If the compare event on channel 1 is set, then branch to OS_Tick_Handler. + * Otherwise, just execute us_ticker_handler. + * This function has to be written in assembly and tagged as naked because OS_Tick_Handler + * will never return. + * A c function would put lr on the stack before calling OS_Tick_Handler and this value + * would never been dequeued. + * + * \code + * void RTC1_IRQHandler(void) { + if(NRF_RTC1->EVENTS_COMPARE[1]) { + // never return... + OS_Tick_Handler(); + } else { + us_ticker_handler(); + } + } + * \endcode + */ + ldr r0,=0x40011144 + ldr r1, [r0, #0] + cmp r1, #0 + beq US_TICKER_HANDLER + bl OS_Tick_Handler +US_TICKER_HANDLER + push {r3, lr} + bl us_ticker_handler + pop {r3, pc} + nop /* padding */ +} + +#elif defined (__GNUC__) /* GNU Compiler */ + +__attribute__((naked)) void RTC1_IRQHandler(void) +{ + /** + * Chanel 1 of RTC1 is used by RTX as a systick. + * If the compare event on channel 1 is set, then branch to OS_Tick_Handler. + * Otherwise, just execute us_ticker_handler. + * This function has to be written in assembly and tagged as naked because OS_Tick_Handler + * will never return. + * A c function would put lr on the stack before calling OS_Tick_Handler and this value + * would never been dequeued. + * + * \code + * void RTC1_IRQHandler(void) { + if(NRF_RTC1->EVENTS_COMPARE[1]) { + // never return... + OS_Tick_Handler(); + } else { + us_ticker_handler(); + } + } + * \endcode + */ + __asm__ ( + "ldr r0,=0x40011144\n" + "ldr r1, [r0, #0]\n" + "cmp r1, #0\n" + "beq US_TICKER_HANDLER\n" + "bl OS_Tick_Handler\n" + "US_TICKER_HANDLER:\n" + "push {r3, lr}\n" + "bl us_ticker_handler\n" + "pop {r3, pc}\n" + "nop" + ); +} + +#else + +#error Compiler not supported. +#error Provide a definition of RTC1_IRQHandler. + +/* + * Chanel 1 of RTC1 is used by RTX as a systick. + * If the compare event on channel 1 is set, then branch to OS_Tick_Handler. + * Otherwise, just execute us_ticker_handler. + * This function has to be written in assembly and tagged as naked because OS_Tick_Handler + * will never return. + * A c function would put lr on the stack before calling OS_Tick_Handler and this value + * will never been dequeued. After a certain time a stack overflow will happen. + * + * \code + * void RTC1_IRQHandler(void) { + if(NRF_RTC1->EVENTS_COMPARE[1]) { + // never return... + OS_Tick_Handler(); + } else { + us_ticker_handler(); + } + } + * \endcode + */ + +#endif + +/** + * Return the next number of clock cycle needed for the next tick. + * @note This function has been carrefuly optimized for a systick occuring every 1000us. + */ +static uint32_t get_next_tick_cc_delta() { + uint32_t delta = 0; + + if (os_clockrate != 1000) { + // In RTX, by default SYSTICK is is used. + // A tick event is generated every os_trv + 1 clock cycles of the system timer. + delta = os_trv + 1; + } else { + // If the clockrate is set to 1000us then 1000 tick should happen every second. + // Unfortunatelly, when clockrate is set to 1000, os_trv is equal to 31. + // If (os_trv + 1) is used as the delta value between two ticks, 1000 ticks will be + // generated in 32000 clock cycle instead of 32768 clock cycles. + // As a result, if a user schedule an OS timer to start in 100s, the timer will start + // instead after 97.656s + // The code below fix this issue, a clock rate of 1000s will generate 1000 ticks in 32768 + // clock cycles. + // The strategy is simple, for 1000 ticks: + // * 768 ticks will occur 33 clock cycles after the previous tick + // * 232 ticks will occur 32 clock cycles after the previous tick + // By default every delta is equal to 33. + // Every five ticks (20%, 200 delta in one second), the delta is equal to 32 + // The remaining (32) deltas equal to 32 are distributed using primes numbers. + static uint32_t counter = 0; + if ((counter % 5) == 0 || (counter % 31) == 0 || (counter % 139) == 0 || (counter == 503)) { + delta = 32; + } else { + delta = 33; + } + ++counter; + if (counter == 1000) { + counter = 0; + } + } + return delta; +} + +static inline void clear_tick_interrupt() { + NRF_RTC1->EVENTS_COMPARE[1] = 0; + NRF_RTC1->EVTENCLR = (1 << 17); +} + +/** + * Indicate if a value is included in a range which can be wrapped. + * @param begin start of the range + * @param end end of the range + * @param val value to check + * @return true if the value is included in the range and false otherwise. + */ +static inline bool is_in_wrapped_range(uint32_t begin, uint32_t end, uint32_t val) { + // regular case, begin < end + // return true if begin <= val < end + if (begin < end) { + if (begin <= val && val < end) { + return false; + } else { + return true; + } + } else { + // In this case end < begin because it has wrap around the limits + // return false if end < val < begin + if (end < val && val < begin) { + return false; + } else { + return true; + } + } + +} + +/** + * Register the next tick. + */ +static void register_next_tick() { + previous_tick_cc_value = NRF_RTC1->CC[1]; + uint32_t delta = get_next_tick_cc_delta(); + uint32_t new_compare_value = (previous_tick_cc_value + delta) & MAX_RTC_COUNTER_VAL; + + // Disable irq directly for few cycles, + // Validation of the new CC value against the COUNTER, + // Setting the new CC value and enabling CC IRQ should be an atomic operation + // Otherwise, there is a possibility to set an invalid CC value because + // the RTC1 keeps running. + // This code is very short 20-38 cycles in the worst case, it shouldn't + // disturb softdevice. + __disable_irq(); + uint32_t current_counter = NRF_RTC1->COUNTER; + + // If an overflow occur, set the next tick in COUNTER + delta clock cycles + if (is_in_wrapped_range(previous_tick_cc_value, new_compare_value, current_counter) == false) { + new_compare_value = current_counter + delta; + } + NRF_RTC1->CC[1] = new_compare_value; + + // set the interrupt of CC channel 1 and reenable IRQs + NRF_RTC1->INTENSET = RTC_INTENSET_COMPARE1_Msk; + __enable_irq(); +} + +/** + * Initialize alternative hardware timer as RTX kernel timer + * This function is directly called by RTX. + * @note this function shouldn't be called directly. + * @return IRQ number of the alternative hardware timer + */ +int os_tick_init (void) +{ + NRF_CLOCK->LFCLKSRC = (CLOCK_LFCLKSRC_SRC_Xtal << CLOCK_LFCLKSRC_SRC_Pos); + NRF_CLOCK->EVENTS_LFCLKSTARTED = 0; + NRF_CLOCK->TASKS_LFCLKSTART = 1; + + while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0) { + // wait for the low frequency clock start + } + + NRF_RTC1->PRESCALER = 0; /* for no pre-scaling. */ + + NVIC_SetPriority(RTC1_IRQn, RTC1_IRQ_PRI); + NVIC_ClearPendingIRQ(RTC1_IRQn); + NVIC_EnableIRQ(RTC1_IRQn); + + NRF_RTC1->TASKS_START = 1; + nrf_delay_us(MAX_RTC_TASKS_DELAY); + + NRF_RTC1->CC[1] = 0; + clear_tick_interrupt(); + register_next_tick(); + + os_tick_started = true; + + return RTC1_IRQn; +} + +/** + * Acknowledge the tick interrupt. + * This function is called by the function OS_Tick_Handler of RTX. + * @note this function shouldn't be called directly. + */ +void os_tick_irqack(void) +{ + clear_tick_interrupt(); + register_next_tick(); +} + +/** + * Returns the overflow flag of the alternative hardware timer. + * @note This function is exposed by RTX kernel. + * @return 1 if the timer has overflowed and 0 otherwise. + */ +uint32_t os_tick_ovf(void) { + uint32_t current_counter = NRF_RTC1->COUNTER; + uint32_t next_tick_cc_value = NRF_RTC1->CC[1]; + + return is_in_wrapped_range(previous_tick_cc_value, next_tick_cc_value, current_counter) ? 0 : 1; +} + +/** + * Return the value of the alternative hardware timer. + * @note The documentation is not very clear about what is expected as a result, + * is it an ascending counter, a descending one ? + * None of this is specified. + * The default systick is a descending counter and this function return values in + * descending order, even if the internal counter used is an ascending one. + * @return the value of the alternative hardware timer. + */ +uint32_t os_tick_val(void) { + uint32_t current_counter = NRF_RTC1->COUNTER; + uint32_t next_tick_cc_value = NRF_RTC1->CC[1]; + + // do not use os_tick_ovf because its counter value can be different + if(is_in_wrapped_range(previous_tick_cc_value, next_tick_cc_value, current_counter)) { + if (next_tick_cc_value > previous_tick_cc_value) { + return next_tick_cc_value - current_counter; + } else if(current_counter <= next_tick_cc_value) { + return next_tick_cc_value - current_counter; + } else { + return next_tick_cc_value + (MAX_RTC_COUNTER_VAL - current_counter); + } + } else { + // use (os_trv + 1) has the base step, can be totally inacurate ... + uint32_t clock_cycles_by_tick = os_trv + 1; + + // if current counter has wrap arround, add the limit to it. + if (current_counter < next_tick_cc_value) { + current_counter = current_counter + MAX_RTC_COUNTER_VAL; + } + + return clock_cycles_by_tick - ((current_counter - next_tick_cc_value) % clock_cycles_by_tick); + } + + return 0; +} diff --git a/rtos/rtx/TARGET_CORTEX_M/RTX_CM_lib.h b/rtos/rtx/TARGET_CORTEX_M/RTX_CM_lib.h index f642ff43c1..6a04fbbfcd 100755 --- a/rtos/rtx/TARGET_CORTEX_M/RTX_CM_lib.h +++ b/rtos/rtx/TARGET_CORTEX_M/RTX_CM_lib.h @@ -178,7 +178,7 @@ osMessageQId osMessageQId_osTimerMessageQ; #endif /* Legacy RTX User Timers not used */ -uint32_t os_tmr = 0U; +uint32_t os_tmr = 0U; uint32_t const *m_tmr = NULL; uint16_t const mp_tmr_size = 0U; @@ -420,6 +420,11 @@ osThreadDef_t os_thread_def_main = {(os_pthread)pre_main, osPriorityNormal, 1U, #elif defined(TARGET_STM32L152RC) #define INITIAL_SP (0x20008000UL) +#elif defined(TARGET_MCU_NORDIC_32K) +#define INITIAL_SP (0x20008000UL) + +#elif defined(TARGET_MCU_NORDIC_16K) +#define INITIAL_SP (0x20004000UL) #else #error "no target defined" diff --git a/rtos/rtx/TARGET_CORTEX_M/RTX_Conf_CM.c b/rtos/rtx/TARGET_CORTEX_M/RTX_Conf_CM.c index 6a24d7cb02..b5dfb09a85 100755 --- a/rtos/rtx/TARGET_CORTEX_M/RTX_Conf_CM.c +++ b/rtos/rtx/TARGET_CORTEX_M/RTX_Conf_CM.c @@ -57,7 +57,7 @@ || defined(TARGET_LPC812) || defined(TARGET_KL25Z) || defined(TARGET_KL26Z) || defined(TARGET_KL27Z) || defined(TARGET_KL05Z) || defined(TARGET_STM32F100RB) || defined(TARGET_STM32F051R8) \ || defined(TARGET_STM32F103RB) || defined(TARGET_LPC824) || defined(TARGET_STM32F302R8) || defined(TARGET_STM32F334R8) || defined(TARGET_STM32F334C8) \ || defined(TARGET_STM32L031K6) || defined(TARGET_STM32L053R8) || defined(TARGET_STM32L053C8) || defined(TARGET_STM32L073RZ) || defined(TARGET_STM32F072RB) || defined(TARGET_STM32F091RC) || defined(TARGET_NZ32_SC151) \ - || defined(TARGET_SSCI824) || defined(TARGET_STM32F030R8) || defined(TARGET_STM32F070RB) + || defined(TARGET_SSCI824) || defined(TARGET_STM32F030R8) || defined(TARGET_STM32F070RB) || defined(TARGET_MCU_NRF51822) # define OS_TASKCNT 6 # else # error "no target defined" @@ -91,7 +91,7 @@ || defined(TARGET_STM32F103RB) || defined(TARGET_LPC824) || defined(TARGET_STM32F302R8) || defined(TARGET_STM32F072RB) || defined(TARGET_STM32F091RC) || defined(TARGET_NZ32_SC151) \ || defined(TARGET_SSCI824) || defined(TARGET_STM32F030R8) || defined(TARGET_STM32F070RB) # define OS_MAINSTKSIZE 128 -# elif defined(TARGET_STM32F334R8) || defined(TARGET_STM32F303RE) || defined(TARGET_STM32F303K8) || defined(TARGET_STM32F334C8) || defined(TARGET_STM32L031K6) || defined(TARGET_STM32L053R8) || defined(TARGET_STM32L053C8) || defined(TARGET_STM32L073RZ) +# elif defined(TARGET_STM32F334R8) || defined(TARGET_STM32F303RE) || defined(TARGET_STM32F303K8) || defined(TARGET_STM32F334C8) || defined(TARGET_STM32L031K6) || defined(TARGET_STM32L053R8) || defined(TARGET_STM32L053C8) || defined(TARGET_STM32L073RZ) || defined(TARGET_MCU_NRF51822) # define OS_MAINSTKSIZE 112 # else # error "no target defined" @@ -105,7 +105,7 @@ #ifndef OS_PRIVCNT #define OS_PRIVCNT 0 #endif - + // Total stack size [bytes] for threads with user-provided stack size <0-1048576:8><#/4> // Defines the combined stack size for threads with user-provided stack size. // Default: 0 @@ -120,16 +120,16 @@ #ifndef OS_STKCHECK #define OS_STKCHECK 1 #endif - + // Stack usage watermark // Initialize thread stack with watermark pattern for analyzing stack usage (current/maximum) in System and Thread Viewer. // Enabling this option increases significantly the execution time of osThreadCreate. #ifndef OS_STKINIT #define OS_STKINIT 0 #endif - -// Processor mode for thread execution -// <0=> Unprivileged mode + +// Processor mode for thread execution +// <0=> Unprivileged mode // <1=> Privileged mode // Default: Privileged mode #ifndef OS_RUNPRIV @@ -137,19 +137,23 @@ #endif // - + // RTX Kernel Timer Tick Configuration // ====================================== // Use Cortex-M SysTick timer as RTX Kernel Timer -// Cortex-M processors provide in most cases a SysTick timer that can be used as +// Cortex-M processors provide in most cases a SysTick timer that can be used as // as time-base for RTX. #ifndef OS_SYSTICK - #define OS_SYSTICK 1 +# if defined(TARGET_MCU_NRF51822) +# define OS_SYSTICK 0 +# else +# define OS_SYSTICK 1 +# endif #endif // // RTOS Kernel Timer input clock frequency [Hz] <1-1000000000> -// Defines the input frequency of the RTOS Kernel Timer. -// When the Cortex-M SysTick timer is used, the input clock +// Defines the input frequency of the RTOS Kernel Timer. +// When the Cortex-M SysTick timer is used, the input clock // is on most systems identical with the core clock. #ifndef OS_CLOCK # if defined(TARGET_LPC1768) || defined(TARGET_LPC2368) || defined(TARGET_TEENSY3_1) @@ -240,11 +244,14 @@ #elif defined(TARGET_STM32L152RC) # define OS_CLOCK 24000000 +#elif defined(TARGET_MCU_NRF51822) +# define OS_CLOCK 32768 + # else # error "no target defined" # endif #endif - + // RTX Timer tick interval value [us] <1-1000000> // The RTX Timer tick interval value is used to calculate timeout values. // When the Cortex-M SysTick timer is enabled, the value also configures the SysTick timer. @@ -292,14 +299,14 @@ #ifndef OS_TIMERPRIO #define OS_TIMERPRIO 5 #endif - + // Timer Thread stack size [bytes] <64-4096:8><#/4> // Defines stack size for Timer thread. // Default: 200 #ifndef OS_TIMERSTKSZ #define OS_TIMERSTKSZ 200 #endif - + // Timer Callback Queue size <1-32> // Number of concurrent active timer callback functions. // Default: 4