You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
355 lines
10 KiB
355 lines
10 KiB
/* |
|
* Interrupt and PWM utilities for 16 bit Timer1 on ATmega168/328 |
|
* Original code by Jesse Tane for http://labs.ideo.com August 2008 |
|
* Modified March 2009 by Jérôme Despatis and Jesse Tane for ATmega328 support |
|
* Modified June 2009 by Michael Polli and Jesse Tane to fix a bug in setPeriod() which caused the timer to stop |
|
* Modified April 2012 by Paul Stoffregen - portable to other AVR chips, use inline functions |
|
* Modified again, June 2014 by Paul Stoffregen - support Teensy 3.x & even more AVR chips |
|
* This version has been downloaded from https://github.com/PaulStoffregen/TimerOne (commit aaeb4a36f5f23e99e6011bea77cf76abb912e13a) |
|
* |
|
* |
|
* This is free software. You can redistribute it and/or modify it under |
|
* the terms of Creative Commons Attribution 3.0 United States License. |
|
* To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/us/ |
|
* or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. |
|
* |
|
*/ |
|
|
|
#ifndef TimerOne_h_ |
|
#define TimerOne_h_ |
|
|
|
#if defined(ARDUINO) && ARDUINO >= 100 |
|
#include "Arduino.h" |
|
#else |
|
#include "WProgram.h" |
|
#endif |
|
|
|
#include "config/known_16bit_timers.h" |
|
|
|
#define TIMER1_RESOLUTION 65536UL // Timer1 is 16 bit |
|
|
|
|
|
// Placing nearly all the code in this .h file allows the functions to be |
|
// inlined by the compiler. In the very common case with constant values |
|
// the compiler will perform all calculations and simply write constants |
|
// to the hardware registers (for example, setPeriod). |
|
|
|
|
|
class TimerOne |
|
{ |
|
|
|
|
|
#if defined(__AVR__) |
|
public: |
|
//**************************** |
|
// Configuration |
|
//**************************** |
|
void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) { |
|
TCCR1B = _BV(WGM13); // set mode as phase and frequency correct pwm, stop the timer |
|
TCCR1A = 0; // clear control register A |
|
setPeriod(microseconds); |
|
} |
|
void setPeriod(unsigned long microseconds) __attribute__((always_inline)) { |
|
const unsigned long cycles = (F_CPU / 2000000) * microseconds; |
|
if (cycles < TIMER1_RESOLUTION) { |
|
clockSelectBits = _BV(CS10); |
|
pwmPeriod = cycles; |
|
} else |
|
if (cycles < TIMER1_RESOLUTION * 8) { |
|
clockSelectBits = _BV(CS11); |
|
pwmPeriod = cycles / 8; |
|
} else |
|
if (cycles < TIMER1_RESOLUTION * 64) { |
|
clockSelectBits = _BV(CS11) | _BV(CS10); |
|
pwmPeriod = cycles / 64; |
|
} else |
|
if (cycles < TIMER1_RESOLUTION * 256) { |
|
clockSelectBits = _BV(CS12); |
|
pwmPeriod = cycles / 256; |
|
} else |
|
if (cycles < TIMER1_RESOLUTION * 1024) { |
|
clockSelectBits = _BV(CS12) | _BV(CS10); |
|
pwmPeriod = cycles / 1024; |
|
} else { |
|
clockSelectBits = _BV(CS12) | _BV(CS10); |
|
pwmPeriod = TIMER1_RESOLUTION - 1; |
|
} |
|
ICR1 = pwmPeriod; |
|
TCCR1B = _BV(WGM13) | clockSelectBits; |
|
} |
|
|
|
//**************************** |
|
// Run Control |
|
//**************************** |
|
void start() __attribute__((always_inline)) { |
|
TCCR1B = 0; |
|
TCNT1 = 0; // TODO: does this cause an undesired interrupt? |
|
resume(); |
|
} |
|
void stop() __attribute__((always_inline)) { |
|
TCCR1B = _BV(WGM13); |
|
} |
|
void restart() __attribute__((always_inline)) { |
|
start(); |
|
} |
|
void resume() __attribute__((always_inline)) { |
|
TCCR1B = _BV(WGM13) | clockSelectBits; |
|
} |
|
|
|
//**************************** |
|
// PWM outputs |
|
//**************************** |
|
void setPwmDuty(char pin, unsigned int duty) __attribute__((always_inline)) { |
|
unsigned long dutyCycle = pwmPeriod; |
|
dutyCycle *= duty; |
|
dutyCycle >>= 10; |
|
if (pin == TIMER1_A_PIN) OCR1A = dutyCycle; |
|
#ifdef TIMER1_B_PIN |
|
else if (pin == TIMER1_B_PIN) OCR1B = dutyCycle; |
|
#endif |
|
#ifdef TIMER1_C_PIN |
|
else if (pin == TIMER1_C_PIN) OCR1C = dutyCycle; |
|
#endif |
|
} |
|
void pwm(char pin, unsigned int duty) __attribute__((always_inline)) { |
|
if (pin == TIMER1_A_PIN) { pinMode(TIMER1_A_PIN, OUTPUT); TCCR1A |= _BV(COM1A1); } |
|
#ifdef TIMER1_B_PIN |
|
else if (pin == TIMER1_B_PIN) { pinMode(TIMER1_B_PIN, OUTPUT); TCCR1A |= _BV(COM1B1); } |
|
#endif |
|
#ifdef TIMER1_C_PIN |
|
else if (pin == TIMER1_C_PIN) { pinMode(TIMER1_C_PIN, OUTPUT); TCCR1A |= _BV(COM1C1); } |
|
#endif |
|
setPwmDuty(pin, duty); |
|
TCCR1B = _BV(WGM13) | clockSelectBits; |
|
} |
|
void pwm(char pin, unsigned int duty, unsigned long microseconds) __attribute__((always_inline)) { |
|
if (microseconds > 0) setPeriod(microseconds); |
|
pwm(pin, duty); |
|
} |
|
void disablePwm(char pin) __attribute__((always_inline)) { |
|
if (pin == TIMER1_A_PIN) TCCR1A &= ~_BV(COM1A1); |
|
#ifdef TIMER1_B_PIN |
|
else if (pin == TIMER1_B_PIN) TCCR1A &= ~_BV(COM1B1); |
|
#endif |
|
#ifdef TIMER1_C_PIN |
|
else if (pin == TIMER1_C_PIN) TCCR1A &= ~_BV(COM1C1); |
|
#endif |
|
} |
|
|
|
//**************************** |
|
// Interrupt Function |
|
//**************************** |
|
void attachInterrupt(void (*isr)()) __attribute__((always_inline)) { |
|
isrCallback = isr; |
|
TIMSK1 = _BV(TOIE1); |
|
} |
|
void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) { |
|
if(microseconds > 0) setPeriod(microseconds); |
|
attachInterrupt(isr); |
|
} |
|
void detachInterrupt() __attribute__((always_inline)) { |
|
TIMSK1 = 0; |
|
} |
|
static void (*isrCallback)(); |
|
static void isrDefaultUnused(); |
|
|
|
private: |
|
// properties |
|
static unsigned short pwmPeriod; |
|
static unsigned char clockSelectBits; |
|
|
|
|
|
|
|
|
|
|
|
|
|
#elif defined(__arm__) && defined(CORE_TEENSY) |
|
|
|
#if defined(KINETISK) |
|
#define F_TIMER F_BUS |
|
#elif defined(KINETISL) |
|
#define F_TIMER (F_PLL/2) |
|
#endif |
|
|
|
public: |
|
//**************************** |
|
// Configuration |
|
//**************************** |
|
void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) { |
|
setPeriod(microseconds); |
|
} |
|
void setPeriod(unsigned long microseconds) __attribute__((always_inline)) { |
|
const unsigned long cycles = (F_TIMER / 2000000) * microseconds; |
|
// A much faster if-else |
|
// This is like a binary serch tree and no more than 3 conditions are evaluated. |
|
// I haven't checked if this becomes significantly longer ASM than the simple ladder. |
|
// It looks very similar to the ladder tho: same # of if's and else's |
|
|
|
/* |
|
// This code does not work properly in all cases :( |
|
// https://github.com/PaulStoffregen/TimerOne/issues/17 |
|
if (cycles < TIMER1_RESOLUTION * 16) { |
|
if (cycles < TIMER1_RESOLUTION * 4) { |
|
if (cycles < TIMER1_RESOLUTION) { |
|
clockSelectBits = 0; |
|
pwmPeriod = cycles; |
|
}else{ |
|
clockSelectBits = 1; |
|
pwmPeriod = cycles >> 1; |
|
} |
|
}else{ |
|
if (cycles < TIMER1_RESOLUTION * 8) { |
|
clockSelectBits = 3; |
|
pwmPeriod = cycles >> 3; |
|
}else{ |
|
clockSelectBits = 4; |
|
pwmPeriod = cycles >> 4; |
|
} |
|
} |
|
}else{ |
|
if (cycles > TIMER1_RESOLUTION * 64) { |
|
if (cycles > TIMER1_RESOLUTION * 128) { |
|
clockSelectBits = 7; |
|
pwmPeriod = TIMER1_RESOLUTION - 1; |
|
}else{ |
|
clockSelectBits = 7; |
|
pwmPeriod = cycles >> 7; |
|
} |
|
} |
|
else{ |
|
if (cycles > TIMER1_RESOLUTION * 32) { |
|
clockSelectBits = 6; |
|
pwmPeriod = cycles >> 6; |
|
}else{ |
|
clockSelectBits = 5; |
|
pwmPeriod = cycles >> 5; |
|
} |
|
} |
|
} |
|
*/ |
|
if (cycles < TIMER1_RESOLUTION) { |
|
clockSelectBits = 0; |
|
pwmPeriod = cycles; |
|
} else |
|
if (cycles < TIMER1_RESOLUTION * 2) { |
|
clockSelectBits = 1; |
|
pwmPeriod = cycles >> 1; |
|
} else |
|
if (cycles < TIMER1_RESOLUTION * 4) { |
|
clockSelectBits = 2; |
|
pwmPeriod = cycles >> 2; |
|
} else |
|
if (cycles < TIMER1_RESOLUTION * 8) { |
|
clockSelectBits = 3; |
|
pwmPeriod = cycles >> 3; |
|
} else |
|
if (cycles < TIMER1_RESOLUTION * 16) { |
|
clockSelectBits = 4; |
|
pwmPeriod = cycles >> 4; |
|
} else |
|
if (cycles < TIMER1_RESOLUTION * 32) { |
|
clockSelectBits = 5; |
|
pwmPeriod = cycles >> 5; |
|
} else |
|
if (cycles < TIMER1_RESOLUTION * 64) { |
|
clockSelectBits = 6; |
|
pwmPeriod = cycles >> 6; |
|
} else |
|
if (cycles < TIMER1_RESOLUTION * 128) { |
|
clockSelectBits = 7; |
|
pwmPeriod = cycles >> 7; |
|
} else { |
|
clockSelectBits = 7; |
|
pwmPeriod = TIMER1_RESOLUTION - 1; |
|
} |
|
|
|
uint32_t sc = FTM1_SC; |
|
FTM1_SC = 0; |
|
FTM1_MOD = pwmPeriod; |
|
FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_CPWMS | clockSelectBits | (sc & FTM_SC_TOIE); |
|
} |
|
|
|
//**************************** |
|
// Run Control |
|
//**************************** |
|
void start() __attribute__((always_inline)) { |
|
stop(); |
|
FTM1_CNT = 0; |
|
resume(); |
|
} |
|
void stop() __attribute__((always_inline)) { |
|
FTM1_SC = FTM1_SC & (FTM_SC_TOIE | FTM_SC_CPWMS | FTM_SC_PS(7)); |
|
} |
|
void restart() __attribute__((always_inline)) { |
|
start(); |
|
} |
|
void resume() __attribute__((always_inline)) { |
|
FTM1_SC = (FTM1_SC & (FTM_SC_TOIE | FTM_SC_PS(7))) | FTM_SC_CPWMS | FTM_SC_CLKS(1); |
|
} |
|
|
|
//**************************** |
|
// PWM outputs |
|
//**************************** |
|
void setPwmDuty(char pin, unsigned int duty) __attribute__((always_inline)) { |
|
unsigned long dutyCycle = pwmPeriod; |
|
dutyCycle *= duty; |
|
dutyCycle >>= 10; |
|
if (pin == TIMER1_A_PIN) { |
|
FTM1_C0V = dutyCycle; |
|
} else if (pin == TIMER1_B_PIN) { |
|
FTM1_C1V = dutyCycle; |
|
} |
|
} |
|
void pwm(char pin, unsigned int duty) __attribute__((always_inline)) { |
|
setPwmDuty(pin, duty); |
|
if (pin == TIMER1_A_PIN) { |
|
*portConfigRegister(TIMER1_A_PIN) = PORT_PCR_MUX(3) | PORT_PCR_DSE | PORT_PCR_SRE; |
|
} else if (pin == TIMER1_B_PIN) { |
|
*portConfigRegister(TIMER1_B_PIN) = PORT_PCR_MUX(3) | PORT_PCR_DSE | PORT_PCR_SRE; |
|
} |
|
} |
|
void pwm(char pin, unsigned int duty, unsigned long microseconds) __attribute__((always_inline)) { |
|
if (microseconds > 0) setPeriod(microseconds); |
|
pwm(pin, duty); |
|
} |
|
void disablePwm(char pin) __attribute__((always_inline)) { |
|
if (pin == TIMER1_A_PIN) { |
|
*portConfigRegister(TIMER1_A_PIN) = 0; |
|
} else if (pin == TIMER1_B_PIN) { |
|
*portConfigRegister(TIMER1_B_PIN) = 0; |
|
} |
|
} |
|
|
|
//**************************** |
|
// Interrupt Function |
|
//**************************** |
|
void attachInterrupt(void (*isr)()) __attribute__((always_inline)) { |
|
isrCallback = isr; |
|
FTM1_SC |= FTM_SC_TOIE; |
|
NVIC_ENABLE_IRQ(IRQ_FTM1); |
|
} |
|
void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) { |
|
if(microseconds > 0) setPeriod(microseconds); |
|
attachInterrupt(isr); |
|
} |
|
void detachInterrupt() __attribute__((always_inline)) { |
|
FTM1_SC &= ~FTM_SC_TOIE; |
|
NVIC_DISABLE_IRQ(IRQ_FTM1); |
|
} |
|
static void (*isrCallback)(); |
|
static void isrDefaultUnused(); |
|
|
|
private: |
|
// properties |
|
static unsigned short pwmPeriod; |
|
static unsigned char clockSelectBits; |
|
|
|
#undef F_TIMER |
|
|
|
#endif |
|
}; |
|
|
|
extern TimerOne Timer1; |
|
|
|
#endif |
|
|
|
|