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
pull/1854/head
Vincent Coubard 2016-05-27 09:33:37 +01:00
parent b32f7a9aaf
commit 000e04d768
3 changed files with 374 additions and 25 deletions

View File

@ -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;
}

View File

@ -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"

View File

@ -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
// <o>Total stack size [bytes] for threads with user-provided stack size <0-1048576:8><#/4>
// <i> Defines the combined stack size for threads with user-provided stack size.
// <i> Default: 0
@ -120,16 +120,16 @@
#ifndef OS_STKCHECK
#define OS_STKCHECK 1
#endif
// <q>Stack usage watermark
// <i> Initialize thread stack with watermark pattern for analyzing stack usage (current/maximum) in System and Thread Viewer.
// <i> Enabling this option increases significantly the execution time of osThreadCreate.
#ifndef OS_STKINIT
#define OS_STKINIT 0
#endif
// <o>Processor mode for thread execution
// <0=> Unprivileged mode
// <o>Processor mode for thread execution
// <0=> Unprivileged mode
// <1=> Privileged mode
// <i> Default: Privileged mode
#ifndef OS_RUNPRIV
@ -137,19 +137,23 @@
#endif
// </h>
// <h>RTX Kernel Timer Tick Configuration
// ======================================
// <q> Use Cortex-M SysTick timer as RTX Kernel Timer
// <i> Cortex-M processors provide in most cases a SysTick timer that can be used as
// <i> Cortex-M processors provide in most cases a SysTick timer that can be used as
// <i> 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
//
// <o>RTOS Kernel Timer input clock frequency [Hz] <1-1000000000>
// <i> Defines the input frequency of the RTOS Kernel Timer.
// <i> When the Cortex-M SysTick timer is used, the input clock
// <i> Defines the input frequency of the RTOS Kernel Timer.
// <i> When the Cortex-M SysTick timer is used, the input clock
// <i> 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
// <o>RTX Timer tick interval value [us] <1-1000000>
// <i> The RTX Timer tick interval value is used to calculate timeout values.
// <i> 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
// <o>Timer Thread stack size [bytes] <64-4096:8><#/4>
// <i> Defines stack size for Timer thread.
// <i> Default: 200
#ifndef OS_TIMERSTKSZ
#define OS_TIMERSTKSZ 200
#endif
// <o>Timer Callback Queue size <1-32>
// <i> Number of concurrent active timer callback functions.
// <i> Default: 4