From dd981aa44d5ecf0d8cd20a5810d8d34416a7fdd2 Mon Sep 17 00:00:00 2001 From: Mike C Date: Wed, 24 Apr 2013 22:49:26 -0400 Subject: [PATCH] Added interrupt driven Timer for serial IO --- Libraries/Timer3/TimerThree.cpp | 102 ++++++++++++++++++ Libraries/Timer3/TimerThree.h | 41 +++++++ .../Libraries/Timer3/TimerThree.cpp | 102 ++++++++++++++++++ .../Libraries/Timer3/TimerThree.h | 41 +++++++ Universal_Serial_Adapter/Project.h | 3 + .../Universal_Serial_Adapter.ino | 19 +++- 6 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 Libraries/Timer3/TimerThree.cpp create mode 100644 Libraries/Timer3/TimerThree.h create mode 100644 Universal_Serial_Adapter/Libraries/Timer3/TimerThree.cpp create mode 100644 Universal_Serial_Adapter/Libraries/Timer3/TimerThree.h diff --git a/Libraries/Timer3/TimerThree.cpp b/Libraries/Timer3/TimerThree.cpp new file mode 100644 index 0000000..df7f308 --- /dev/null +++ b/Libraries/Timer3/TimerThree.cpp @@ -0,0 +1,102 @@ +/* + * Interrupt and PWM utilities for 16 bit Timer3 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 Oct 2009 by Dan Clemens to work with timer3 of the ATMega1280 or Arduino Mega + * + * 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. + * + */ + +#include "TimerThree.h" + +TimerThree Timer3; // preinstatiate + +ISR(TIMER3_OVF_vect) // interrupt service routine that wraps a user defined function supplied by attachInterrupt +{ + Timer3.isrCallback(); +} + +void TimerThree::initialize(long microseconds) +{ + TCCR3A = 0; // clear control register A + TCCR3B = _BV(WGM13); // set mode as phase and frequency correct pwm, stop the timer + setPeriod(microseconds); +} + +void TimerThree::setPeriod(long microseconds) +{ + long cycles = (F_CPU * microseconds) / 2000000; // the counter runs backwards after TOP, interrupt is at BOTTOM so divide microseconds by 2 + if(cycles < RESOLUTION) clockSelectBits = _BV(CS10); // no prescale, full xtal + else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11); // prescale by /8 + else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11) | _BV(CS10); // prescale by /64 + else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12); // prescale by /256 + else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12) | _BV(CS10); // prescale by /1024 + else cycles = RESOLUTION - 1, clockSelectBits = _BV(CS12) | _BV(CS10); // request was out of bounds, set as maximum + ICR3 = pwmPeriod = cycles; // ICR1 is TOP in p & f correct pwm mode + TCCR3B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12)); + TCCR3B |= clockSelectBits; // reset clock select register +} + +void TimerThree::setPwmDuty(char pin, int duty) +{ + unsigned long dutyCycle = pwmPeriod; + dutyCycle *= duty; + dutyCycle >>= 10; + if(pin == 5) OCR3A = dutyCycle; + if(pin == 2) OCR3B = dutyCycle; + if(pin == 3) OCR3C = dutyCycle; +} + +void TimerThree::pwm(char pin, int duty, long microseconds) // expects duty cycle to be 10 bit (1024) +{ + if(microseconds > 0) setPeriod(microseconds); + + // sets data direction register for pwm output pin + // activates the output pin + if(pin == 5) { DDRE |= _BV(PORTE3); TCCR3A |= _BV(COM3A1); } + if(pin == 2) { DDRE |= _BV(PORTE4); TCCR3A |= _BV(COM3B1); } + if(pin == 3) { DDRE |= _BV(PORTE5); TCCR3A |= _BV(COM3C1); } + setPwmDuty(pin, duty); + start(); +} + +void TimerThree::disablePwm(char pin) +{ + if(pin == 5) TCCR3A &= ~_BV(COM3A1); // clear the bit that enables pwm on PE3 + if(pin == 2) TCCR3A &= ~_BV(COM3B1); // clear the bit that enables pwm on PE4 + if(pin == 3) TCCR3A &= ~_BV(COM3C1); // clear the bit that enables pwm on PE5 +} + +void TimerThree::attachInterrupt(void (*isr)(), long microseconds) +{ + if(microseconds > 0) setPeriod(microseconds); + isrCallback = isr; // register the user's callback with the real ISR + TIMSK3 = _BV(TOIE1); // sets the timer overflow interrupt enable bit + sei(); // ensures that interrupts are globally enabled + start(); +} + +void TimerThree::detachInterrupt() +{ + TIMSK3 &= ~_BV(TOIE1); // clears the timer overflow interrupt enable bit +} + +void TimerThree::start() +{ + TCCR3B |= clockSelectBits; +} + +void TimerThree::stop() +{ + TCCR3B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12)); // clears all clock selects bits +} + +void TimerThree::restart() +{ + TCNT3 = 0; +} diff --git a/Libraries/Timer3/TimerThree.h b/Libraries/Timer3/TimerThree.h new file mode 100644 index 0000000..60ea3f1 --- /dev/null +++ b/Libraries/Timer3/TimerThree.h @@ -0,0 +1,41 @@ +/* + * Interrupt and PWM utilities for 16 bit Timer3 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 + * + * 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. + * + */ + +#include +#include + +#define RESOLUTION 65536 // Timer3 is 16 bit + +class TimerThree +{ + public: + + // properties + unsigned int pwmPeriod; + unsigned char clockSelectBits; + + // methods + void initialize(long microseconds=1000000); + void start(); + void stop(); + void restart(); + void pwm(char pin, int duty, long microseconds=-1); + void disablePwm(char pin); + void attachInterrupt(void (*isr)(), long microseconds=-1); + void detachInterrupt(); + void setPeriod(long microseconds); + void setPwmDuty(char pin, int duty); + void (*isrCallback)(); +}; + +extern TimerThree Timer3; diff --git a/Universal_Serial_Adapter/Libraries/Timer3/TimerThree.cpp b/Universal_Serial_Adapter/Libraries/Timer3/TimerThree.cpp new file mode 100644 index 0000000..df7f308 --- /dev/null +++ b/Universal_Serial_Adapter/Libraries/Timer3/TimerThree.cpp @@ -0,0 +1,102 @@ +/* + * Interrupt and PWM utilities for 16 bit Timer3 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 Oct 2009 by Dan Clemens to work with timer3 of the ATMega1280 or Arduino Mega + * + * 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. + * + */ + +#include "TimerThree.h" + +TimerThree Timer3; // preinstatiate + +ISR(TIMER3_OVF_vect) // interrupt service routine that wraps a user defined function supplied by attachInterrupt +{ + Timer3.isrCallback(); +} + +void TimerThree::initialize(long microseconds) +{ + TCCR3A = 0; // clear control register A + TCCR3B = _BV(WGM13); // set mode as phase and frequency correct pwm, stop the timer + setPeriod(microseconds); +} + +void TimerThree::setPeriod(long microseconds) +{ + long cycles = (F_CPU * microseconds) / 2000000; // the counter runs backwards after TOP, interrupt is at BOTTOM so divide microseconds by 2 + if(cycles < RESOLUTION) clockSelectBits = _BV(CS10); // no prescale, full xtal + else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11); // prescale by /8 + else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11) | _BV(CS10); // prescale by /64 + else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12); // prescale by /256 + else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12) | _BV(CS10); // prescale by /1024 + else cycles = RESOLUTION - 1, clockSelectBits = _BV(CS12) | _BV(CS10); // request was out of bounds, set as maximum + ICR3 = pwmPeriod = cycles; // ICR1 is TOP in p & f correct pwm mode + TCCR3B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12)); + TCCR3B |= clockSelectBits; // reset clock select register +} + +void TimerThree::setPwmDuty(char pin, int duty) +{ + unsigned long dutyCycle = pwmPeriod; + dutyCycle *= duty; + dutyCycle >>= 10; + if(pin == 5) OCR3A = dutyCycle; + if(pin == 2) OCR3B = dutyCycle; + if(pin == 3) OCR3C = dutyCycle; +} + +void TimerThree::pwm(char pin, int duty, long microseconds) // expects duty cycle to be 10 bit (1024) +{ + if(microseconds > 0) setPeriod(microseconds); + + // sets data direction register for pwm output pin + // activates the output pin + if(pin == 5) { DDRE |= _BV(PORTE3); TCCR3A |= _BV(COM3A1); } + if(pin == 2) { DDRE |= _BV(PORTE4); TCCR3A |= _BV(COM3B1); } + if(pin == 3) { DDRE |= _BV(PORTE5); TCCR3A |= _BV(COM3C1); } + setPwmDuty(pin, duty); + start(); +} + +void TimerThree::disablePwm(char pin) +{ + if(pin == 5) TCCR3A &= ~_BV(COM3A1); // clear the bit that enables pwm on PE3 + if(pin == 2) TCCR3A &= ~_BV(COM3B1); // clear the bit that enables pwm on PE4 + if(pin == 3) TCCR3A &= ~_BV(COM3C1); // clear the bit that enables pwm on PE5 +} + +void TimerThree::attachInterrupt(void (*isr)(), long microseconds) +{ + if(microseconds > 0) setPeriod(microseconds); + isrCallback = isr; // register the user's callback with the real ISR + TIMSK3 = _BV(TOIE1); // sets the timer overflow interrupt enable bit + sei(); // ensures that interrupts are globally enabled + start(); +} + +void TimerThree::detachInterrupt() +{ + TIMSK3 &= ~_BV(TOIE1); // clears the timer overflow interrupt enable bit +} + +void TimerThree::start() +{ + TCCR3B |= clockSelectBits; +} + +void TimerThree::stop() +{ + TCCR3B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12)); // clears all clock selects bits +} + +void TimerThree::restart() +{ + TCNT3 = 0; +} diff --git a/Universal_Serial_Adapter/Libraries/Timer3/TimerThree.h b/Universal_Serial_Adapter/Libraries/Timer3/TimerThree.h new file mode 100644 index 0000000..60ea3f1 --- /dev/null +++ b/Universal_Serial_Adapter/Libraries/Timer3/TimerThree.h @@ -0,0 +1,41 @@ +/* + * Interrupt and PWM utilities for 16 bit Timer3 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 + * + * 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. + * + */ + +#include +#include + +#define RESOLUTION 65536 // Timer3 is 16 bit + +class TimerThree +{ + public: + + // properties + unsigned int pwmPeriod; + unsigned char clockSelectBits; + + // methods + void initialize(long microseconds=1000000); + void start(); + void stop(); + void restart(); + void pwm(char pin, int duty, long microseconds=-1); + void disablePwm(char pin); + void attachInterrupt(void (*isr)(), long microseconds=-1); + void detachInterrupt(); + void setPeriod(long microseconds); + void setPwmDuty(char pin, int duty); + void (*isrCallback)(); +}; + +extern TimerThree Timer3; diff --git a/Universal_Serial_Adapter/Project.h b/Universal_Serial_Adapter/Project.h index 06740be..3c68be8 100644 --- a/Universal_Serial_Adapter/Project.h +++ b/Universal_Serial_Adapter/Project.h @@ -33,6 +33,9 @@ #define voltagePinThreePointThree 41 // Pin controlling 3.3V TTL logic #define voltagePinFivePointZero 42 // Pin controlling 5V TTL logic +// Timer pins +#define timerThreePin 2 // Maps to Mega pin 12 + // Colors / theme of UI #define SPLASH_BACKGROUND ST7735_WHITE #define BACKGROUND ST7735_BLACK diff --git a/Universal_Serial_Adapter/Universal_Serial_Adapter.ino b/Universal_Serial_Adapter/Universal_Serial_Adapter.ino index dcd3b55..84dc949 100644 --- a/Universal_Serial_Adapter/Universal_Serial_Adapter.ino +++ b/Universal_Serial_Adapter/Universal_Serial_Adapter.ino @@ -22,9 +22,16 @@ #include #include +#include + UI* ui; Config* config; +// helper for interrupt method call +void processSerial() { + config->processSerialData(); +} + void setup() { Serial.begin(115200); @@ -32,15 +39,17 @@ void setup() { config->setDefaults(); ui = new UI(); + + // Setup serial IO on an interrupt timer + // ***THIS MUST BE DONE AFTER new Config()*** + Timer3.initialize(100); // initialize timer3, value in micro seconds + Timer3.pwm(timerThreePin, 512); // setup pwm on appropriate pin, 50% duty cycle + Timer3.attachInterrupt(processSerial); // attaches method as a timer overflow interrupt } void loop() { - // Serial data is processed at multiple points to prevent - // UI code from interfering with communication - config->processSerialData(); + // Serial data is processed via an interrupt so isn't needed in the main loop ui->processInputEvents(); - config->processSerialData(); ui->processTimeoutEvents(); - config->processSerialData(); }