/* * Copyright (c) 2014, Ralf Schlatterbeck Open Source Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file is part of the Contiki operating system. */ /** * \defgroup hardware timer * * This module wraps hardware timers of AVR microcontrollers. * Currently we only support 16-bit timers. The main focus is currently * on PWM generation. But input capture and interrupt routines are on * the TODO list, see below. We currently support the AVR ATmega128RFA1 * so this should be generalized to supported timers of other AVR * microcontrollers. * * Datasheet references in the following refer to ATmega128RFA1 data sheet * * TODO: Allow input capture. * TODO: Allow definition of interrupt routine; check if merkur board * supports necessary pins. * TODO: Generalize for 8-bit timers. * TODO: Check other AVR microcontrollers and the supported timers. * * @{ */ /** * \file * Header file for hardware timer of AVR microcontrollers * \author * Ralf Schlatterbeck * */ #ifndef hw_timer_h #define hw_timer_h #include "contiki.h" #include "rtimer-arch.h" #ifndef PLAT_TIMER #define PLAT_TIMER -1 #endif /* * All routines return a negative number for error and 0 for success. * The negative return value indicates the error. */ #define HWT_ERR_INVALID_TIMER (-1) #define HWT_ERR_INVALID_WGM (-2) #define HWT_ERR_INVALID_COM (-3) #define HWT_ERR_INVALID_CLOCK (-4) #define HWT_ERR_INVALID_CHANNEL (-5) /* * Timer waveform generation modes (WGM), see data sheet * chapter 18 "16-bit Timer/Counter (Timer/Counter 1,3,4, and 5) * 18.9 "Modes of Operation", in particular Table 18-5 */ #define HWT_WGM_NORMAL 0 #define HWT_WGM_PWM_PHASE_8_BIT 1 #define HWT_WGM_PWM_PHASE_9_BIT 2 #define HWT_WGM_PWM_PHASE_10_BIT 3 #define HWT_WGM_CTC_OCRA 4 #define HWT_WGM_PWM_FAST_8_BIT 5 #define HWT_WGM_PWM_FAST_9_BIT 6 #define HWT_WGM_PWM_FAST_10_BIT 7 #define HWT_WGM_PWM_PHASE_FRQ_ICR 8 #define HWT_WGM_PWM_PHASE_FRQ_OCRA 9 #define HWT_WGM_PWM_PHASE_ICR 10 #define HWT_WGM_PWM_PHASE_OCRA 11 #define HWT_WGM_CTC_ICR 12 #define HWT_WGM_RESERVED 13 #define HWT_WGM_PWM_FAST_ICR 14 #define HWT_WGM_PWM_FAST_OCRA 15 #define HWT_WGM_MASK 15 #define HWT_WGM_MASK_LOW 3 #define HWT_WGM_MASK_HIGH (HWT_WGM_MASK - HWT_WGM_MASK_LOW) #define HWT_WGM_SHIFT_LOW 0 #define HWT_WGM_SHIFT_HIGH 1 /* * Timer compare output modes (COM), * chapter 18 "16-bit Timer/Counter (Timer/Counter 1,3,4, and 5) * 18.8 "Compare Match Output Unit", in particular Tables 18-2,3,4 */ #define HWT_COM_NORMAL 0 #define HWT_COM_TOGGLE 1 #define HWT_COM_CLEAR 2 #define HWT_COM_SET 3 #define HWT_COM_MASK 3 /* * Clock select, clock can be off, use prescaler or external clock * source on Tn pin. See Table 18-11 (for Timer 1 but this is the same * for all the timers). */ #define HWT_CLOCK_OFF 0 #define HWT_CLOCK_PRESCALER_1 1 #define HWT_CLOCK_PRESCALER_8 2 #define HWT_CLOCK_PRESCALER_64 3 #define HWT_CLOCK_PRESCALER_256 4 #define HWT_CLOCK_PRESCALER_1024 5 #define HWT_CLOCK_EXTERN_FALLING 6 #define HWT_CLOCK_EXTERN_RISING 7 #define HWT_CLOCK_MASK 7 /* * Timer channels A B C */ #define HWT_CHANNEL_A 0 #define HWT_CHANNEL_B 1 #define HWT_CHANNEL_C 2 #define HWT_CHANNEL_D 3 #define HWT_CHANNEL_MASK 3 /* The following macros are defined for timer values 1,3,4,5 */ #define HWT_ICR(t) \ ((t)<4?((t)==1?(&ICR1) :(&ICR3)) :((t)==4?(&ICR4) :(&ICR5))) #define HWT_OCRA(t) \ ((t)<4?((t)==1?(&OCR1A):(&OCR3A)):((t)==4?(&OCR4A):(&OCR5A))) #define HWT_OCRB(t) \ ((t)<4?((t)==1?(&OCR1B):(&OCR3B)):((t)==4?(&OCR4B):(&OCR5B))) #define HWT_OCRC(t) \ ((t)<4?((t)==1?(&OCR1C):(&OCR3C)):((t)==4?(&OCR4C):(&OCR5C))) #define HWT_OCR(t,c) \ ( (c)==HWT_CHANNEL_A \ ? (HWT_OCRA(t)) \ : ((c)==HWT_CHANNEL_B?HWT_OCRB(t):HWT_OCRC(t)) \ ) #define HWT_TCCRA(t) \ ( (t)<4 \ ? ((t)==1?(&TCCR1A):(&TCCR3A)) \ : ((t)==4?(&TCCR4A):(&TCCR5A)) \ ) #define HWT_TCCRB(t) \ ( (t)<4 \ ? ((t)==1?(&TCCR1B):(&TCCR3B)) \ : ((t)==4?(&TCCR4B):(&TCCR5B)) \ ) #define HWT_TCCRC(t) \ ( (t)<4 \ ? ((t)==1?(&TCCR1C):(&TCCR3C)) \ : ((t)==4?(&TCCR4C):(&TCCR5C)) \ ) #define HWT_TCNT(t) \ ( (t)<4 \ ? ((t)==1?(&TCNT1) :(&TCNT3)) \ : ((t)==4?(&TCNT4) :(&TCNT5)) \ ) #define HWT_CLR_COM(timer, channel) \ (*HWT_TCCRA (timer) &= ~(HWT_COM_MASK << (6 - 2 * (channel)))) #define HWT_SET_COM(timer, channel, com) \ ( HWT_CLR_COM (timer, channel) \ , (*HWT_TCCRA (timer) |= ((com) << (6 - 2 * (channel)))) \ ) #define HWT_CHECK_TIMER(timer) \ do { \ if ((timer) == 0 || (timer) == 2 || (timer) == PLAT_TIMER || (timer) > 5) {\ return HWT_ERR_INVALID_TIMER; \ } \ } while (0) #define HWT_CHECK_CHANNEL(chan) \ do { \ if ((chan) > HWT_CHANNEL_C) { \ return HWT_ERR_INVALID_CHANNEL; \ } \ } while (0) #define HWT_PWM_FAST 0 #define HWT_PWM_PHASE_CORRECT 1 #define HWT_PWM_PHASE_FRQ_CORRECT 2 /** * \brief Initialize the hardware timer with the given settings * \param timer: Timer to use * \param wgm: waveform generation mode to use, see definitions * \param clock: Prescaler or external clock settings * \param maxt: Maximum counter value, not used for fixed modes, this * sets ICRn for the ICR modes and OCRnA for the OCR modes * \return see HWT_ERR definitions for return codes, returns 0 if ok * * The initial compare output mode is set to HWT_COM_NORMAL (off) for * all outputs (pwm disabled). * * Note that this sets the compare output mode COM registers to 0, * turning off PWM on outputs. */ static inline int8_t hwtimer_ini (uint8_t timer, uint8_t wgm, uint8_t clock, uint16_t maxt) { int8_t i; HWT_CHECK_TIMER (timer); if (wgm > HWT_WGM_MASK || wgm == HWT_WGM_RESERVED) { return HWT_ERR_INVALID_WGM; } if (clock > HWT_CLOCK_MASK) { return HWT_ERR_INVALID_CLOCK; } /* Turn off clock, no need to disable interrupt */ *HWT_TCCRB (timer) &= ~HWT_CLOCK_MASK; *HWT_TCCRA (timer) &= ~(HWT_WGM_MASK_LOW << HWT_WGM_SHIFT_LOW); *HWT_TCCRA (timer) |= ((wgm & HWT_WGM_MASK_LOW) << HWT_WGM_SHIFT_LOW); *HWT_TCCRB (timer) &= ~(HWT_WGM_MASK_HIGH << HWT_WGM_SHIFT_HIGH); *HWT_TCCRB (timer) |= ((wgm & HWT_WGM_MASK_HIGH) << HWT_WGM_SHIFT_HIGH); for (i=0; i<3; i++) { HWT_CLR_COM (timer, i); } if ( wgm == HWT_WGM_PWM_PHASE_FRQ_ICR || wgm == HWT_WGM_PWM_PHASE_ICR || wgm == HWT_WGM_CTC_ICR || wgm == HWT_WGM_PWM_FAST_ICR ) { *HWT_ICR (timer) = maxt; } if ( wgm == HWT_WGM_CTC_OCRA || wgm == HWT_WGM_PWM_PHASE_FRQ_OCRA || wgm == HWT_WGM_PWM_PHASE_OCRA || wgm == HWT_WGM_PWM_FAST_OCRA ) { *HWT_OCRA (timer) = maxt; } /* Set clock, finally */ *HWT_TCCRB (timer) |= clock; return 0; } /* Needed for implementation */ #define HWT_PERIOD_MAX_ (0xFFFFFFFF / (F_CPU / 1000000)) /* for 16-bit timer: */ #define HWT_TICKS_MAX_ 0xFFFF #define HWT_TICKS_MIN_ 0xFF /** * \brief Convenience function to initialize hardware timer for PWM * \param timer: Timer to use * \param pwm_type: See HWT_PWM* macros * \param period_us: Period of the timer in µs * \param ocra: Use OCRnA register if set, ICRn otherwise * \return see HWT_ERR definitions for return codes, returns 0 if ok * * This function can be called instead of hwtimer_ini and sets up the * timer for one of the PWM modes. There are fast, phase-correct and * phase- and frequency correct modes, refer to the datasheet for * semantics. * * The function tries to initialize the timer to a mode that doesn't use * one of the internal registers OCRnA or ICRn for specifying the upper * bound of the counter. For fast PWM and phase-correct PWM there are * fixed 8-, 9-, and 10-bit modes that can be used if the computed value * fits one of these setups. * * We try to get the *maximum* prescaler that still permits a tick * resolution of at least 8 bit. This will not work for very high * frequencies. * * If the specified period is too large to fit into a 16-bit timer we * take the maximum period that is still possible, this may be * substatially higher than specified. * * Note that when using OCRnA for the upper bound of the counter, the * pin associated with this register can not be used for PWM. Instead it * can be used to change the period. */ static inline int8_t hwtimer_pwm_ini (uint8_t timer, uint32_t period_us, uint8_t pwm_type, uint8_t ocra) { uint32_t ticks = 0; uint8_t clock = HWT_CLOCK_PRESCALER_1024; uint8_t wgm = HWT_WGM_NORMAL; HWT_CHECK_TIMER (timer); if (period_us > HWT_PERIOD_MAX_) { period_us = HWT_PERIOD_MAX_; } ticks = (F_CPU / 1000000) * period_us; /* Non-fast PWM modes have half the frequency */ if (pwm_type != HWT_PWM_FAST) { ticks >>= 1; } /* * Divisors are 1, 8, 64, 256, 1024, shifts between these are * 3, 3, 2, 2, respectively. We modify `ticks` in place, the AVR can * shift only one bit in one instruction, so shifting isn't cheap. * We try to get the *maximum* prescaler that still permits a tick * resolution of at least 8 bit. */ if (ticks <= (HWT_TICKS_MIN_ << 3)) { clock = HWT_CLOCK_PRESCALER_1; } else if ((ticks >>= 3) <= (HWT_TICKS_MIN_ << 3)) { clock = HWT_CLOCK_PRESCALER_8; } else if ((ticks >>= 3) <= (HWT_TICKS_MIN_ << 2)) { clock = HWT_CLOCK_PRESCALER_64; } else if ((ticks >>= 2) <= (HWT_TICKS_MIN_ << 2)) { clock = HWT_CLOCK_PRESCALER_256; } else if ((ticks >>= 2) > HWT_TICKS_MAX_) { ticks = HWT_TICKS_MAX_; } switch (pwm_type) { case HWT_PWM_FAST: wgm = ocra ? HWT_WGM_PWM_FAST_OCRA : HWT_WGM_PWM_FAST_ICR; break; case HWT_PWM_PHASE_CORRECT: wgm = ocra ? HWT_WGM_PWM_PHASE_OCRA : HWT_WGM_PWM_PHASE_ICR; break; case HWT_PWM_PHASE_FRQ_CORRECT: default: wgm = ocra ? HWT_WGM_PWM_PHASE_FRQ_OCRA : HWT_WGM_PWM_PHASE_FRQ_ICR; break; } /* Special 8- 9- 10-bit modes */ if (pwm_type == HWT_PWM_FAST || pwm_type == HWT_PWM_PHASE_CORRECT) { if (ticks == 0xFF) { wgm = (pwm_type == HWT_PWM_FAST) ? HWT_WGM_PWM_FAST_8_BIT : HWT_WGM_PWM_PHASE_8_BIT; } else if (ticks == 0x1FF) { wgm = (pwm_type == HWT_PWM_FAST) ? HWT_WGM_PWM_FAST_9_BIT : HWT_WGM_PWM_PHASE_9_BIT; } else if (ticks == 0x3FF) { wgm = (pwm_type == HWT_PWM_FAST) ? HWT_WGM_PWM_FAST_10_BIT : HWT_WGM_PWM_PHASE_10_BIT; } } return hwtimer_ini (timer, wgm, clock, ticks); } /* * Simple init macro for sane default values */ #define hwtimer_pwm_ini_simple (timer, period_us) \ hwtimer_pwm_ini ((timer), HWT_PWM_PHASE_CORRECT, (period_us), 0) /** * \brief Maximum timer value usable in hwtimer_set_pwm * \param timer: Timer to use * \return max. timer value according to current timer setup * negative value if wrong timer given * a positive value is guaranteed to fit into 16 bit unsigned. */ static inline int32_t hwtimer_pwm_max_ticks (uint8_t timer) { uint8_t wgm = 0; HWT_CHECK_TIMER (timer); wgm = ((*HWT_TCCRA (timer) >> HWT_WGM_SHIFT_LOW) & HWT_WGM_MASK_LOW) | ((*HWT_TCCRB (timer) >> HWT_WGM_SHIFT_HIGH) & HWT_WGM_MASK_HIGH) ; switch (wgm) { case HWT_WGM_PWM_PHASE_8_BIT: case HWT_WGM_PWM_FAST_8_BIT: return 0xFF; case HWT_WGM_PWM_PHASE_9_BIT: case HWT_WGM_PWM_FAST_9_BIT: return 0x1FF; case HWT_WGM_PWM_PHASE_10_BIT: case HWT_WGM_PWM_FAST_10_BIT: return 0x3FF; case HWT_WGM_CTC_OCRA: case HWT_WGM_PWM_PHASE_FRQ_OCRA: case HWT_WGM_PWM_PHASE_OCRA: case HWT_WGM_PWM_FAST_OCRA: return *HWT_OCRA (timer); case HWT_WGM_PWM_PHASE_FRQ_ICR: case HWT_WGM_PWM_PHASE_ICR: case HWT_WGM_CTC_ICR: case HWT_WGM_PWM_FAST_ICR: return *HWT_ICR (timer); case HWT_WGM_NORMAL: return 0xFFFF; } return HWT_ERR_INVALID_WGM; } /* * The following functions are defined inline to allow for compiler * optimizations if some of the parameters are constant. */ /** * \brief Set PWM duty cycle * \param timer: Timer to use * \param channel: Channel to use, see HWT_CHANNEL definitions * \param pwm: Duty cycle * \return see HWT_ERR definitions for return codes, returns 0 if ok * * Note that the available range for the duty cycle depends on the timer * setup and the chosen mode. */ static inline int8_t hwtimer_set_pwm (uint8_t timer, uint8_t channel, uint16_t pwm) { uint8_t sreg = 0; HWT_CHECK_TIMER (timer); HWT_CHECK_CHANNEL (channel); sreg = SREG; cli (); *HWT_OCR (timer, channel) = pwm; SREG = sreg; return 0; } /** * \brief Set compare output mode * \param timer: Timer to use * \param channel: Channel to use, see HWT_CHANNEL definitions * \param com: compare output mode for given channel * \return see HWT_ERR definitions for return codes, returns 0 if ok */ static inline int8_t hwtimer_set_com (uint8_t timer, uint8_t channel, uint8_t com) { HWT_CHECK_TIMER (timer); HWT_CHECK_CHANNEL (channel); if (com > HWT_COM_MASK) { return HWT_ERR_INVALID_COM; } if (com) { HWT_SET_COM (timer, channel, com); } else { HWT_CLR_COM (timer, channel); } return 0; } /** * \brief Convenience function for setting compare output mode for PWM * \param timer: Timer to use * \param channel: Channel to use, see HWT_CHANNEL definitions * \return see HWT_ERR definitions for return codes, returns 0 if ok */ static inline int8_t hwtimer_pwm_enable (uint8_t timer, uint8_t channel) { return hwtimer_set_com (timer, channel, HWT_COM_CLEAR); } /** * \brief Convenience function for inverse compare output mode for PWM * \param timer: Timer to use * \param channel: Channel to use, see HWT_CHANNEL definitions * \return see HWT_ERR definitions for return codes, returns 0 if ok */ static inline int8_t hwtimer_pwm_inverse (uint8_t timer, uint8_t channel) { return hwtimer_set_com (timer, channel, HWT_COM_SET); } /** * \brief Convenience function for setting compare output mode to off * \param timer: Timer to use * \param channel: Channel to use, see HWT_CHANNEL definitions * \return see HWT_ERR definitions for return codes, returns 0 if ok */ static inline int8_t hwtimer_pwm_disable (uint8_t timer, uint8_t channel) { return hwtimer_set_com (timer, channel, HWT_COM_NORMAL); } /** * \brief Turn off the clock * \param timer: Timer to use * \return see HWT_ERR definitions for return codes, returns 0 if ok */ static inline int8_t hwtimer_fin (uint8_t timer) { HWT_CHECK_TIMER (timer); *HWT_TCCRB (timer) &= ~HWT_CLOCK_MASK; *HWT_TCCRB (timer) |= HWT_CLOCK_OFF; /* technically not necessary this is 0 */ return 0; } #endif /* hw_timer_h */ /* * ex:ts=8:et:sw=2 */ /** @} */