From afb01e230fdaa8df13ba3d285d10f759216e704b Mon Sep 17 00:00:00 2001 From: Sasan Golchin <ahmad.golchin@mail.polimi.it> Date: Tue, 11 Oct 2016 14:23:23 +0200 Subject: [PATCH] miosix/arch/cortexM3_stm32/common/interfaces-impl/cstimer.cpp --- .../common/interfaces-impl/cstimer.cpp | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 miosix/arch/cortexM3_stm32/common/interfaces-impl/cstimer.cpp diff --git a/miosix/arch/cortexM3_stm32/common/interfaces-impl/cstimer.cpp b/miosix/arch/cortexM3_stm32/common/interfaces-impl/cstimer.cpp new file mode 100644 index 00000000..4b78f8d4 --- /dev/null +++ b/miosix/arch/cortexM3_stm32/common/interfaces-impl/cstimer.cpp @@ -0,0 +1,180 @@ + +#include "interfaces/cstimer.h" +#include "interfaces/arch_registers.h" +#include "kernel/kernel.h" +#include "kernel/scheduler/timer_interrupt.h" +#include "kernel/timeconversion.h" + +using namespace miosix; + +const unsigned int timerBits=16; +const unsigned long long overflowIncrement=(1LL<<timerBits); +const unsigned long long lowerMask=overflowIncrement-1; +const unsigned long long upperMask=0xFFFFFFFFFFFFFFFFLL-lowerMask; + +static long long ms32time = 0; //most significant 32 bits of counter +static long long ms32chkp = 0; //most significant 32 bits of check point +static bool lateIrq=false; + +static TimeConversion *tc; + +static inline long long nextInterrupt() +{ + return ms32chkp | TIM2->CCR1; +} + +static inline long long IRQgetTick() +{ + //THE PENDING BIT TRICK, version 2 + //This algorithm is the main part that allows to extend in software a + //32bit timer to a 64bit one. The basic idea is this: the lower bits of the + //64bit timer are kept by the counter register of the timer, while the upper + //bits are kept in a software variable. When the hardware timer overflows, + //an interrupt is used to update the upper bits. + //Reading the timer may appear to be doable by just an OR operation between + //the software variable and the hardware counter, but is actually way + //trickier than it seems, because user code may: + //1 disable interrupts, + //2 spend a little time with interrupts disabled, + //3 call this function. + //Now, if a timer overflow occurs while interrupts are disabled, the upper + //bits have not yet been updated, so we would return the wrong time. + //To fix this, we check the timer overflow pending bit, and if it is set + //we return the time adjusted accordingly. This almost works, the last + //issue to fix is that reading the timer counter and the pending bit + //is not an atomic operation, and the counter may roll over exactly at that + //point in time. To solve this, we read the timer a second time to see if + //it had rolled over. + //Note that this algorithm imposes a limit on the maximum time interrupts + //can be disabeld, equals to one hardware timer period minus the time + //between the two timer reads in this algorithm. + unsigned int counter=TIM2->CNT; + if((TIM2->SR & TIM_SR_UIF) && TIM2->CNT>=counter) + return (ms32time | static_cast<long long>(counter)) + overflowIncrement; + return ms32time | static_cast<long long>(counter); +} + +void __attribute__((naked)) TIM2_IRQHandler() +{ + saveContext(); + asm volatile ("bl _Z9cstirqhndv"); + restoreContext(); +} + +void __attribute__((used)) cstirqhnd() +{ + if(TIM2->SR & TIM_SR_CC1IF || lateIrq) + { + TIM2->SR = ~TIM_SR_CC1IF; + if(ms32time==ms32chkp || lateIrq) + { + lateIrq=false; + IRQtimerInterrupt(tc->tick2ns(IRQgetTick())); + } + + } + //Rollover + if(TIM2->SR & TIM_SR_UIF) + { + TIM2->SR = ~TIM_SR_UIF; + ms32time += overflowIncrement; + } +} + +// +// class ContextSwitchTimer +// + +namespace miosix { + +ContextSwitchTimer& ContextSwitchTimer::instance() +{ + static ContextSwitchTimer instance; + return instance; +} + +void ContextSwitchTimer::IRQsetNextInterrupt(long long ns) +{ + long long tick = tc->ns2tick(ns); + ms32chkp = tick & upperMask; + TIM2->CCR1 = static_cast<unsigned int>(tick & lowerMask); + if(IRQgetTick() >= nextInterrupt()) + { + NVIC_SetPendingIRQ(TIM2_IRQn); + lateIrq=true; + } +} + +long long ContextSwitchTimer::getNextInterrupt() const +{ + return tc->tick2ns(nextInterrupt()); +} + +long long ContextSwitchTimer::getCurrentTick() const +{ + bool interrupts=areInterruptsEnabled(); + //TODO: optimization opportunity, if we can guarantee that no call to this + //function occurs before kernel is started, then we can use + //fastInterruptDisable()) + if(interrupts) disableInterrupts(); + long long result=tc->tick2ns(IRQgetTick()); + if(interrupts) enableInterrupts(); + return result; +} + +long long ContextSwitchTimer::IRQgetCurrentTick() const +{ + return tc->tick2ns(IRQgetTick()); +} + +ContextSwitchTimer::~ContextSwitchTimer() {} + +ContextSwitchTimer::ContextSwitchTimer() +{ + // TIM2 Source Clock (from APB1) Enable + { + //NOTE: Not FastInterruptDisableLock as this is called before kernel + //is started + InterruptDisableLock idl; + RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; + RCC_SYNC(); + //DBGMCU->APB1FZ|=DBGMCU_APB1_FZ_DBG_TIM2_STOP; //Tim2 stops while debugging + } + // Setup TIM2 base configuration + // Mode: Up-counter + // Interrupts: counter overflow, Compare/Capture on channel 1 + TIM2->CR1=TIM_CR1_URS; + TIM2->DIER=TIM_DIER_UIE | TIM_DIER_CC1IE; + NVIC_SetPriority(TIM2_IRQn,3); //High priority for TIM2 (Max=0, min=15) + NVIC_EnableIRQ(TIM2_IRQn); + // Configure channel 1 as: + // Output channel (CC1S=0) + // No preload(OC1PE=0), hence TIM2_CCR1 can be written at anytime + // No effect on the output signal on match (OC1M = 0) + TIM2->CCMR1 = 0; + TIM2->CCR1 = 0; + // TIM2 Operation Frequency Configuration: 100Hz as the freq. and longest period + TIM2->PSC = 239; + TIM2->ARR = 0xFFFF; + + // Enable TIM2 Counter + TIM2->EGR = TIM_EGR_UG; //To enforce the timer to apply PSC + TIM2->CR1 |= TIM_CR1_CEN; + + // The global variable SystemCoreClock from ARM's CMSIS allows to know + // the CPU frequency. + timerFreq=SystemCoreClock; + + // The timer frequency may however be a submultiple of the CPU frequency, + // due to the bus at whch the periheral is connected being slower. The + // RCC->CFGR register tells us how slower the APB1 bus is running. + // This formula takes into account that if the APB1 clock is divided by a + // factor of two or greater, the timer is clocked at twice the bus + // interface. After this, the freq variable contains the frequency in Hz + // at which the timer prescaler is clocked. + if(RCC->CFGR & RCC_CFGR_PPRE1_2) timerFreq/=1<<((RCC->CFGR>>10) & 0x3); + timerFreq /= TIM2->PSC + 1; + static TimeConversion stc(timerFreq); + tc = &stc; +} +} //namespace miosix -- GitLab