/*************************************************************************** * Copyright (C) 2014 by Terraneo Federico * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * As a special exception, if other files instantiate templates or use * * macros or inline functions from this file, or you compile this file * * and link it with other works to produce a work based on this file, * * this file does not by itself cause the resulting work to be covered * * by the GNU General Public License. However the source code for this * * file must still be made available in accordance with the GNU General * * Public License. This exception does not invalidate any other reasons * * why a work based on this file might be covered by the GNU General * * Public License. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see * ***************************************************************************/ #include "servo_stm32.h" #include "kernel/scheduler/scheduler.h" #include #include #include using namespace std; using namespace miosix; typedef Gpio servo1out; typedef Gpio servo2out; typedef Gpio servo3out; typedef Gpio servo4out; static Thread *waiting=0; /** * Timer 4 interrupt handler actual implementation */ void __attribute__((used)) tim4impl() { TIM4->SR=0; //Clear interrupt flag if(waiting==0) return; waiting->IRQwakeup(); if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) Scheduler::IRQfindNextThread(); waiting=0; } /** * Timer 4 interrupt handler */ void __attribute__((naked)) TIM4_IRQHandler() { saveContext(); asm volatile("bl _Z8tim4implv"); restoreContext(); } namespace miosix { /* TODO: find a better place for this */ unsigned int divideRoundToNearest(unsigned int a, unsigned int b) { const unsigned int quot=2*a/b; return quot/2 + (quot & 1); } // // class SynchronizedServo // SynchronizedServo& SynchronizedServo::instance() { static SynchronizedServo singleton; return singleton; } void SynchronizedServo::enable(int channel) { Lock l(mutex); if(status!=STOPPED) return; // If timer enabled ignore the call { FastInterruptDisableLock dLock; // Calling the mode() function on a GPIO is subject to race conditions // between threads on the STM32, so we disable interrupts switch(channel) { case 0: TIM4->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE; TIM4->CCER |= TIM_CCER_CC1E; #ifndef _ARCH_CORTEXM3_STM32 //Only stm32f2 and stm32f4 have it servo1out::alternateFunction(2); #endif //_ARCH_CORTEXM3_STM32 servo1out::mode(Mode::ALTERNATE); break; case 1: TIM4->CCMR1 |= TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE; TIM4->CCER |= TIM_CCER_CC2E; #ifndef _ARCH_CORTEXM3_STM32 //Only stm32f2 and stm32f4 have it servo2out::alternateFunction(2); #endif //_ARCH_CORTEXM3_STM32 servo2out::mode(Mode::ALTERNATE); break; case 2: TIM4->CCMR2 |= TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE; TIM4->CCER |= TIM_CCER_CC3E; #ifndef _ARCH_CORTEXM3_STM32 //Only stm32f2 and stm32f4 have it servo3out::alternateFunction(2); #endif //_ARCH_CORTEXM3_STM32 servo3out::mode(Mode::ALTERNATE); break; case 3: TIM4->CCMR2 |= TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4PE; TIM4->CCER |= TIM_CCER_CC4E; #ifndef _ARCH_CORTEXM3_STM32 //Only stm32f2 and stm32f4 have it servo4out::alternateFunction(2); #endif //_ARCH_CORTEXM3_STM32 servo4out::mode(Mode::ALTERNATE); break; } } } void SynchronizedServo::disable(int channel) { Lock l(mutex); if(status!=STOPPED) return; // If timer enabled ignore the call { FastInterruptDisableLock dLock; // Calling the mode() function on a GPIO is subject to race conditions // between threads on the STM32, so we disable interrupts switch(channel) { case 0: servo1out::mode(Mode::INPUT); TIM4->CCER &= ~TIM_CCER_CC1E; break; case 1: servo2out::mode(Mode::INPUT); TIM4->CCER &= ~TIM_CCER_CC2E; break; case 2: servo3out::mode(Mode::INPUT); TIM4->CCER &= ~TIM_CCER_CC3E; break; case 3: servo4out::mode(Mode::INPUT); TIM4->CCER &= ~TIM_CCER_CC4E; break; } } } void SynchronizedServo::setPosition(int channel, float value) { Lock l(mutex); if(status!=STARTED) return; // If timer disabled ignore the call value=min(1.0f,max(0.0f,value)); switch(channel) { case 0: TIM4->CCR1=a*value+b; break; case 1: TIM4->CCR2=a*value+b; break; case 2: TIM4->CCR3=a*value+b; break; case 3: TIM4->CCR4=a*value+b; break; } } float SynchronizedServo::getPosition(int channel) { switch(channel) { case 0: return TIM4->CCR1==0 ? NAN : TIM4->CCR1/a-b; case 1: return TIM4->CCR2==0 ? NAN : TIM4->CCR2/a-b; case 2: return TIM4->CCR3==0 ? NAN : TIM4->CCR3/a-b; case 3: return TIM4->CCR4==0 ? NAN : TIM4->CCR4/a-b; default: return NAN; } } void SynchronizedServo::start() { Lock l(mutex); if(status!=STOPPED) return; // If timer enabled ignore the call // While status is starting neither memeber function callable with timer // started nor stopped are allowed status=STARTED; TIM4->CNT=0; TIM4->EGR=TIM_EGR_UG; TIM4->CR1=TIM_CR1_CEN; } void SynchronizedServo::stop() { Lock l(mutex); if(status!=STARTED) return; // If timer disabled ignore the call status=STOPPED; // Stopping the timer is a bit difficult because we don't want to // accidentally create glitches on the outputs which may turn the servos // to random positions. So, we set all PWM outputs to 0, then wait until the // end of the period, and then disable the timer TIM4->CCR1=0; TIM4->CCR2=0; TIM4->CCR3=0; TIM4->CCR4=0; { FastInterruptDisableLock dLock; // Wakeup an eventual thread waiting on waitForCycleBegin() if(waiting) waiting->IRQwakeup(); IRQwaitForTimerOverflow(dLock); } TIM4->CR1=0; } bool SynchronizedServo::waitForCycleBegin() { // No need to lock the mutex because disabling interrupts is enough to avoid // race conditions. Also, locking the mutex here would prevent other threads // from calling other member functions of this class FastInterruptDisableLock dLock; if(status!=STARTED) return true; IRQwaitForTimerOverflow(dLock); return status!=STARTED; } void SynchronizedServo::setFrequency(unsigned int frequency) { Lock l(mutex); if(status!=STOPPED) return; // If timer enabled ignore the call TIM4->PSC=divideRoundToNearest(getPrescalerInputFrequency(),frequency*65536)-1; precomputeCoefficients(); } float SynchronizedServo::getFrequency() const { float prescalerFreq=getPrescalerInputFrequency(); return prescalerFreq/((TIM4->PSC+1)*65536); } void SynchronizedServo::setMinPulseWidth(float minPulse) { Lock l(mutex); if(status!=STOPPED) return; // If timer enabled ignore the call minWidth=1e-6f*min(1300.0f,max(500.0f,minPulse)); precomputeCoefficients(); } void SynchronizedServo::setMaxPulseWidth(float maxPulse) { Lock l(mutex); if(status!=STOPPED) return; // If timer enabled ignore the call maxWidth=1e-6f*min(2500.0f,max(1700.0f,maxPulse)); precomputeCoefficients(); } SynchronizedServo::SynchronizedServo() : status(STOPPED) { { FastInterruptDisableLock dLock; // The RCC register should be written with interrupts disabled to // prevent race conditions with other threads. RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; RCC_SYNC(); } // Configure timer TIM4->CR1=0; TIM4->ARR=0xffff; TIM4->CCR1=0; TIM4->CCR2=0; TIM4->CCR3=0; TIM4->CCR4=0; // Configure interrupt on timer overflow TIM4->DIER=TIM_DIER_UIE; NVIC_SetPriority(TIM4_IRQn,13); //Low priority for timer IRQ NVIC_EnableIRQ(TIM4_IRQn); // Set default parameters setFrequency(50); setMinPulseWidth(1000); setMaxPulseWidth(2000); } void SynchronizedServo::precomputeCoefficients() { float realPeriod=1.0f/getFrequency(); a=65536.0f*(maxWidth-minWidth)/realPeriod; b=65536.0f*minWidth/realPeriod; } unsigned int SynchronizedServo::getPrescalerInputFrequency() { // The global variable SystemCoreClock from ARM's CMSIS allows to know // the CPU frequency. unsigned int freq=SystemCoreClock; //The position of the PPRE1 bit in RCC->CFGR is different in some stm32 #ifdef _ARCH_CORTEXM3_STM32 const unsigned int ppre1=8; #else //stm32f2 and f4 const unsigned int ppre1=10; #endif // 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) freq/=1<<((RCC->CFGR>>ppre1) & 0x3); return freq; } void SynchronizedServo::IRQwaitForTimerOverflow(FastInterruptDisableLock& dLock) { waiting=Thread::IRQgetCurrentThread(); do { Thread::IRQwait(); { FastInterruptEnableLock eLock(dLock); Thread::yield(); } } while(waiting); } } //namespace miosix