// Copyright 2011 Olivier Gillet.
//
// Author: Olivier Gillet (ol.gillet@gmail.com)
//
// 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 3 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.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
// -----------------------------------------------------------------------------
//
// Timer/counter
#ifndef AVRLIBX_SYSTEM_TIMER_H_
#define AVRLIBX_SYSTEM_TIMER_H_
#include
#include "avrlibx/avrlibx.h"
#include "avrlibx/io/gpio.h"
#include "avrlibx/system/event_system.h"
namespace avrlibx {
enum TimerMode {
TIMER_MODE_NORMAL = 0,
TIMER_MODE_FREQUENCY_GENERATOR = 1,
TIMER_MODE_SINGLE_PWM = 3,
TIMER_MODE_DUAL_PWM_T = 5,
TIMER_MODE_DUAL_PWM_TB = 6,
TIMER_MODE_DUAL_PWM_B = 7
};
enum TimerPrescaler {
TIMER_PRESCALER_OFF = 0,
TIMER_PRESCALER_CLK = 1,
TIMER_PRESCALER_CLK_2 = 2,
TIMER_PRESCALER_CLK_4 = 3,
TIMER_PRESCALER_CLK_8 = 4,
TIMER_PRESCALER_CLK_64 = 5,
TIMER_PRESCALER_CLK_256 = 6,
TIMER_PRESCALER_CLK_1024 = 7,
};
enum TimerChannel {
TIMER_CHANNEL_A,
TIMER_CHANNEL_B,
TIMER_CHANNEL_C,
TIMER_CHANNEL_D
};
enum TimerEventAction {
TIMER_EVENT_ACTION_NONE = TC_EVACT_OFF_gc,
TIMER_EVENT_ACTION_CAPTURE = TC_EVACT_CAPT_gc,
TIMER_EVENT_ACTION_UPDOWN = TC_EVACT_UPDOWN_gc,
TIMER_EVENT_ACTION_QDEC = TC_EVACT_QDEC_gc,
TIMER_EVENT_ACTION_RESTART = TC_EVACT_RESTART_gc,
TIMER_EVENT_ACTION_FRQ = TC_EVACT_FRW_gc,
TIMER_EVENT_ACTION_PW = TC_EVACT_PW_gc
};
template struct TCWrapper { };
#define WRAP_TIMER(port, index) \
template<> \
struct TCWrapper { \
static inline TC0_t& tc() { \
return (TC0_t&)(TC ## port ## index); \
} \
static volatile inline uint16_t count() { \
return TC ## port ## index ## _CNT; \
} \
static inline void set_count(uint16_t value) { \
TC ## port ## index ## _CNT = value; \
} \
static inline uint16_t period() { \
return TC ## port ## index ## _PERBUF; \
} \
static inline void set_period(uint16_t value) { \
TC ## port ## index ## _PERBUF = value; \
} \
static inline uint8_t dma_tx_trigger() { \
return DMA_CH_TRIGSRC_TC ## port ## index ## _OVF_gc; \
} \
static inline uint8_t overflow_event() { \
return EVSYS_CHMUX_TC ## port ## index ## _OVF_gc; \
} \
template \
static inline void set_channel(uint16_t value) { \
if (channel == TIMER_CHANNEL_A) { \
TC ## port ## index ## _CCABUF = value; \
} else if (channel == TIMER_CHANNEL_B) { \
TC ## port ## index ## _CCBBUF = value; \
} else if (channel == TIMER_CHANNEL_C && !index) { \
TC ## port ## 0_CCCBUF = value; \
} else if (channel == TIMER_CHANNEL_D && !index) { \
TC ## port ## 0_CCDBUF = value; \
} \
} \
template \
static inline uint16_t get_channel() { \
if (channel == TIMER_CHANNEL_A) { \
return TC ## port ## index ## _CCA; \
} else if (channel == TIMER_CHANNEL_B) { \
return TC ## port ## index ## _CCB; \
} else if (channel == TIMER_CHANNEL_C && !index) { \
return TC ## port ## 0_CCC; \
} else if (channel == TIMER_CHANNEL_D && !index) { \
return TC ## port ## 0_CCD; \
} \
} \
};
WRAP_TIMER(C, 0)
WRAP_TIMER(C, 1)
WRAP_TIMER(D, 0)
WRAP_TIMER(D, 1)
WRAP_TIMER(E, 0)
#ifdef TCE1
WRAP_TIMER(E, 1)
#endif
#ifdef TCF0
WRAP_TIMER(F, 0)
#endif
template
class Timer {
public:
typedef TCWrapper TC;
static inline void set_prescaler(TimerPrescaler prescaler) {
TC::tc().CTRLA = prescaler;
}
static inline void set_mode(TimerMode mode) {
// Preserve Compare/capture enable and set mode.
TC::tc().CTRLB = (TC::tc().CTRLB & 0xf0) | mode;
}
static volatile inline uint16_t count() {
return TC::count();
}
static inline void set_count(uint16_t value) {
TC::set_count(value);
}
static inline uint16_t period() {
return TC::period();
}
static inline void set_period(uint16_t value) {
TC::set_period(value);
}
static inline void Restart() {
TC::tc().CTRLFSET = 8;
}
static inline void Bind(uint8_t channel, TimerEventAction event_action) {
TC::tc().CTRLD = event_action | 0x08 | channel;
}
static inline void EnableCC(uint8_t channel) {
TC::tc().CTRLB |= (16 << channel);
}
static inline void StopCC(uint8_t channel) {
TC::tc().CTRLB &= ~(16 << channel);
}
static inline void set_pwm_resolution(uint8_t resolution) {
TC::set_period((1 << static_cast(resolution) )- 1);
}
static inline void EnableOverflowInterrupt(uint8_t int_level) {
TC::tc().INTCTRLA = (TC::tc().INTCTRLA & 0xfc) | int_level;
}
static inline void DisableOverflowInterrupt() {
TC::tc().INTCTRLA = TC::tc().INTCTRLA & 0xfc;
}
static inline void EnableChannelInterrupt(
uint8_t channel, uint8_t int_level) {
uint8_t shift = channel << 2;
uint8_t mask = (0x3) << shift;
TC::tc().INTCTRLB = (TC::tc().INTCTRLB & ~mask) | (int_level << shift);
}
static inline void DisableChannelInterrupt(uint8_t channel) {
uint8_t shift = channel << 2;
uint8_t mask = (0x3) << shift;
TC::tc().INTCTRLB = TC::tc().INTCTRLB & ~mask;
}
template
static inline void set_channel(uint16_t value) {
TC::template set_channel(value);
}
template
static inline uint16_t get_channel() {
return TC::template get_channel();
}
template
static inline void EnableChannelInterrupt(uint8_t int_level) {
uint8_t shift = channel << 2;
uint8_t mask = (0x3) << shift;
TC::tc().INTCTRLB = (TC::tc().INTCTRLB & ~mask) | (int_level << shift);
}
template
static inline void DisableChannelInterrupt() {
uint8_t shift = channel << 2;
uint8_t mask = (0x3) << shift;
TC::tc().INTCTRLB = TC::tc().INTCTRLB & ~mask;
}
static inline uint8_t dma_tx_trigger() {
//
// Horrible hack ahead!
//
// It looks like a timer overflow cannot be used as a DMA trigger. The
// workaround for this is to use an Event as a proxy: set the event trigger
// to be the timer overflow ; set the DMA trigger to be the event.
// Here we use system event 0 for this purpose.
EVSYS_CH0MUX = TC::overflow_event();
return DMA_CH_TRIGSRC_EVSYS_CH0_gc;
}
};
// To be tested (XMega-AU only)
template
class DualTimer {
public:
typedef TCWrapper TC;
static inline void set_prescaler(TimerPrescaler prescaler) {
TC::tc().CTRLE = 0x2;
TC::tc().CTRLA = prescaler;
}
static inline void EnabledInterrupts(uint8_t level_1, uint8_t level_2) {
TC::tc().INTCTRLA = (level_1 << 2) | level_2;
}
static inline void set_periods(uint8_t period_1, uint8_t period_2) {
TC::tc().HPER = period_1;
TC::tc().LPER = period_2;
}
};
template
struct PWMPinToTimer { };
#define BIND_TIMER_TO_PWM_PIN(port, index, Channel, pin) \
template<> struct PWMPinToTimer { \
typedef Timer T; \
enum { channel = Channel }; \
}; \
BIND_TIMER_TO_PWM_PIN(PortC, 0, TIMER_CHANNEL_A, 0);
BIND_TIMER_TO_PWM_PIN(PortC, 0, TIMER_CHANNEL_B, 1);
BIND_TIMER_TO_PWM_PIN(PortC, 0, TIMER_CHANNEL_C, 2);
BIND_TIMER_TO_PWM_PIN(PortC, 0, TIMER_CHANNEL_D, 3);
BIND_TIMER_TO_PWM_PIN(PortC, 1, TIMER_CHANNEL_A, 4);
BIND_TIMER_TO_PWM_PIN(PortC, 1, TIMER_CHANNEL_B, 5);
BIND_TIMER_TO_PWM_PIN(PortD, 0, TIMER_CHANNEL_A, 0);
BIND_TIMER_TO_PWM_PIN(PortD, 0, TIMER_CHANNEL_B, 1);
BIND_TIMER_TO_PWM_PIN(PortD, 0, TIMER_CHANNEL_C, 2);
BIND_TIMER_TO_PWM_PIN(PortD, 0, TIMER_CHANNEL_D, 3);
BIND_TIMER_TO_PWM_PIN(PortD, 1, TIMER_CHANNEL_A, 4);
BIND_TIMER_TO_PWM_PIN(PortD, 1, TIMER_CHANNEL_B, 5);
BIND_TIMER_TO_PWM_PIN(PortE, 0, TIMER_CHANNEL_A, 0);
BIND_TIMER_TO_PWM_PIN(PortE, 0, TIMER_CHANNEL_B, 1);
BIND_TIMER_TO_PWM_PIN(PortE, 0, TIMER_CHANNEL_C, 2);
BIND_TIMER_TO_PWM_PIN(PortE, 0, TIMER_CHANNEL_D, 3);
#ifdef TCE1
BIND_TIMER_TO_PWM_PIN(PortE, 1, TIMER_CHANNEL_A, 4);
BIND_TIMER_TO_PWM_PIN(PortE, 1, TIMER_CHANNEL_B, 5);
#endif
#ifdef TCF0
BIND_TIMER_TO_PWM_PIN(PortF, 0, TIMER_CHANNEL_A, 0);
BIND_TIMER_TO_PWM_PIN(PortF, 0, TIMER_CHANNEL_B, 1);
BIND_TIMER_TO_PWM_PIN(PortF, 0, TIMER_CHANNEL_C, 2);
BIND_TIMER_TO_PWM_PIN(PortF, 0, TIMER_CHANNEL_D, 3);
#endif
template
class PWM {
public:
static inline void set_value(uint16_t value) {
PWMPinToTimer::T::template \
set_channel::channel>(value);
}
static inline uint16_t get_value() {
return PWMPinToTimer::T::template \
get_channel::channel>();
}
static inline void Init(uint8_t resolution) {
typename PWMPinToTimer::T timer;
timer.set_prescaler(TIMER_PRESCALER_CLK);
timer.set_mode(TIMER_MODE_SINGLE_PWM);
timer.set_pwm_resolution(resolution);
Start();
}
static inline void EnableInterrupt(uint8_t level) {
PWMPinToTimer::T::template \
EnableChannelInterrupt::channel>(level);
}
static inline void DisableInterrupt() {
PWMPinToTimer::T::template \
DisableChannelInterrupt::channel>();
}
static inline void Start() {
Gpio::set_direction(OUTPUT);
PWMPinToTimer::T::EnableCC(PWMPinToTimer::channel);
}
static inline void Stop() {
PWMPinToTimer::T::StopCC(PWMPinToTimer::channel);
}
static inline void Write(uint16_t value) { set_value(value); }
};
template
class InputCapture {
public:
template
static inline void Init(bool frequency_measurement_mode) {
Gpio gpio;
// Set the GPIO to input with rising edge detection, and disable pull-up.
gpio.set_direction(INPUT);
gpio.set_sense(SENSE_MODE_RISING);
gpio.Low();
// The selected event source is the GPIO state change.
EventSystemChannel::set_source(gpio.event());
// Bind this event to the channel capture.
PWMPinToTimer::T::Bind(
event_channel,
frequency_measurement_mode
? TIMER_EVENT_ACTION_FRQ
: TIMER_EVENT_ACTION_CAPTURE);
Start();
}
static inline void EnableInterrupt(uint8_t level) {
PWMPinToTimer::T::template \
EnableChannelInterrupt::channel>(level);
}
static inline void DisableInterrupt() {
PWMPinToTimer::T::template \
DisableChannelInterrupt::channel>();
}
static inline uint16_t get_value() {
return PWMPinToTimer::T::template \
get_channel::channel>();
}
static inline void Start() {
PWMPinToTimer::T::EnableCC(PWMPinToTimer::channel);
}
static inline void Stop() {
PWMPinToTimer::T::StopCC(PWMPinToTimer::channel);
}
};
} // namespace avrlibx
#endif // AVRLIBX_SYSTEM_TIMER_H_