From 24f4da3da3708afffd84b18a1d0eae57e9efe090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Fri, 23 Sep 2016 12:10:35 -0700 Subject: [PATCH 01/18] Correct bug in timer example. --- examples/atmega328p/timer.ino | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/atmega328p/timer.ino b/examples/atmega328p/timer.ino index 47d49d6..7f96140 100644 --- a/examples/atmega328p/timer.ino +++ b/examples/atmega328p/timer.ino @@ -23,5 +23,8 @@ void loop(void) { timer.enable(); devices.select(); - digitalWrite(LED, !digitalRead(LED)); + if (timer.is_ready()) + { + digitalWrite(LED, !digitalRead(LED)); + } } From 22a0c5e7e95045155747080efe3635d3282b1631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Fri, 23 Sep 2016 12:11:32 -0700 Subject: [PATCH 02/18] Correct bug in timer example. --- examples/atmega328p/timer.ino | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/atmega328p/timer.ino b/examples/atmega328p/timer.ino index 47d49d6..7f96140 100644 --- a/examples/atmega328p/timer.ino +++ b/examples/atmega328p/timer.ino @@ -23,5 +23,8 @@ void loop(void) { timer.enable(); devices.select(); - digitalWrite(LED, !digitalRead(LED)); + if (timer.is_ready()) + { + digitalWrite(LED, !digitalRead(LED)); + } } From 8bc51324a0a8600c6d9ac1b4640dca0f62cff15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:26:03 -0800 Subject: [PATCH 03/18] Modify type name. --- pinchange.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pinchange.h b/pinchange.h index 7dceba0..c7fec0b 100644 --- a/pinchange.h +++ b/pinchange.h @@ -1,6 +1,9 @@ #ifndef __PINCHANGE_H__ #define __PINCHANGE_H__ +#include +#include "device.h" + class Pin; class Port { @@ -20,9 +23,9 @@ class Port { return i; } Pin *_pins[8]; - byte _port; - byte _enabled; - volatile byte _state; + uint8_t _port; + uint8_t _enabled; + volatile uint8_t _state; }; class Pin: public Device { From 9c0134dbec1f41d2fa2df3544dd93fbef94d8436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:27:10 -0800 Subject: [PATCH 04/18] Discard the first analog measurement after the ADC is powered up. --- analog.h | 15 +++++++++++---- atmega328p/analog.cpp | 12 ++++++++++-- attiny84/analog.cpp | 12 ++++++++++-- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/analog.h b/analog.h index 8542b0b..f9d8a25 100644 --- a/analog.h +++ b/analog.h @@ -1,16 +1,19 @@ #ifndef __ANALOG_H__ #define __ANALOG_H__ +#include +#include "device.h" + /** * Analog-to-Digital conversion. - * + * * FIXME: analogReadResolution() */ class Analog: public Device { public: // pin is the analog input, e.g., A0-A7 Analog(int pin, unsigned ref = DEFAULT): - Device(pin), _pin(pin), _ref(ref) {} + Device(pin), _pin(pin), _ref(ref), _next_reading_valid(false) {} // not enabled by default bool begin(); @@ -25,8 +28,11 @@ class Analog: public Device { unsigned read(); // call to turn off ADC altogether and back on again - void sleep(); - void wake(); + virtual void sleep(); + virtual void wake(); + + bool next_reading_valid(void) {return _next_reading_valid;} + void next_reading_will_be_valid() {_next_reading_valid = true; } protected: void _enable(bool enabled); @@ -37,6 +43,7 @@ class Analog: public Device { int _pin; unsigned _ref; + volatile bool _next_reading_valid; }; #endif diff --git a/atmega328p/analog.cpp b/atmega328p/analog.cpp index 11f1ca8..0d05ad7 100644 --- a/atmega328p/analog.cpp +++ b/atmega328p/analog.cpp @@ -6,7 +6,7 @@ #include "device.h" #include "analog.h" -static Device *adc; +static Analog *adc; static volatile unsigned reading = 0xffff; ISR(ADC_vect) { @@ -15,7 +15,14 @@ ISR(ADC_vect) { low = ADCL; high = ADCH; reading = (high << 8) | low; - adc->ready(); + if (adc->_next_reading_valid) + adc->ready(); + else + { + // Can't use this conversion so start a new conversion + ADCSRA |= bit(ADSC) | bit(ADIE); + adc->_next_reading_valid = true; + } } } @@ -47,6 +54,7 @@ void Analog::sleep() { void Analog::wake() { power_adc_enable(); + _next_reading_valid = false; // Ignore first reading after power on ADCSRA |= bit(ADEN); ACSR &= ~bit(ACD); } diff --git a/attiny84/analog.cpp b/attiny84/analog.cpp index 25c265f..158b461 100644 --- a/attiny84/analog.cpp +++ b/attiny84/analog.cpp @@ -6,7 +6,7 @@ #include "device.h" #include "analog.h" -static Device *adc; +static Analog *adc; static volatile unsigned reading = 0xffff; ISR(ADC_vect) { @@ -15,7 +15,14 @@ ISR(ADC_vect) { low = ADCL; high = ADCH; reading = (high << 8) | low; - adc->ready(); + if (adc->next_reading_valid()) + adc->ready(); + else + { + // Can't use this conversion so start a new conversion + ADCSRA |= bit(ADSC) | bit(ADIE); + adc->next_reading_will_be_valid(); + } } } @@ -47,6 +54,7 @@ void Analog::sleep() { void Analog::wake() { power_adc_enable(); + _next_reading_valid = false; // Ignore first reading after power on ADCSRA |= bit(ADEN); ACSR &= ~bit(ACD); } From 9e34ddab7566d718a68a2f7b30f1cbf3192ddc96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:30:06 -0800 Subject: [PATCH 05/18] Implement default sleep and wake functions. Initially, all on-chip devices are powered off. --- atmega328p/device.cpp | 18 +++++++++++++++--- attiny84/device.cpp | 23 ++++++++++++++++------- device.h | 15 ++++++++++----- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/atmega328p/device.cpp b/atmega328p/device.cpp index b1a1281..34c341a 100644 --- a/atmega328p/device.cpp +++ b/atmega328p/device.cpp @@ -7,15 +7,21 @@ #include "device.h" +// Ensure that these are defined properly (there appears to be a bug in avr/power.h for ATMega328[P]) +#define power_all_enable() (PRR &= (uint8_t)~((1<enable(_devices[i]->begin()); + { + if (_devices[i]->begin()) + { + _devices[i]->wake(); + _devices[i]->enable(true); + } + } sei(); } diff --git a/attiny84/device.cpp b/attiny84/device.cpp index 97e0658..c852cfc 100644 --- a/attiny84/device.cpp +++ b/attiny84/device.cpp @@ -3,22 +3,25 @@ #include #include -#include #include "device.h" -#define BODS 7 // BOD Sleep bit in MCUCR +#define BODS 7 // BOD Sleep bit in MCUCR #define BODSE 2 // BOD Sleep enable bit in MCUCR +#ifndef SLEEP_MODE_STANDBY +#define SLEEP_MODE_STANDBY SLEEP_MODE_PWR_SAVE +#endif + void Devices::begin() { - // "...[it] is therefore required to turn off the watchdog + // "...[it] is therefore required to turn off the watchdog // early during program startup..." (from avr/wdt.h) wdt_disable(); // turn off ADC and analog comparator ADCSRA = 0; ACSR |= bit(ACD); - power_adc_disable(); // FIXME: power_all_disable()? + power_all_disable(); // turn off the brown-out detector MCUCR |= _BV(BODS) | _BV(BODSE); @@ -27,7 +30,13 @@ void Devices::begin() { digitalWrite(i, LOW); for (int i = 0; i < _n; i++) - _devices[i]->enable(_devices[i]->begin()); + { + if (_devices[i]->begin()) + { + _devices[i]->wake(); + _devices[i]->enable(true); + } + } sei(); } @@ -45,9 +54,9 @@ unsigned Devices::compare_modes(unsigned sys, unsigned dev) { if (sys != SLEEP_MODE_IDLE) return SLEEP_MODE_ADC; break; - case SLEEP_MODE_PWR_SAVE: + case SLEEP_MODE_STANDBY: if (sys != SLEEP_MODE_IDLE && sys != SLEEP_MODE_ADC) - return SLEEP_MODE_PWR_SAVE; + return SLEEP_MODE_STANDBY; break; case SLEEP_MODE_PWR_DOWN: break; diff --git a/device.h b/device.h index c3ccc9a..9373b0a 100644 --- a/device.h +++ b/device.h @@ -28,7 +28,7 @@ class Devices { } static void sleep(unsigned mode); -private: + int _n; Device *_devices[MAX_DEVICES]; }; @@ -38,9 +38,9 @@ class Device { // devices which are enabled at startup return true here virtual bool begin() =0; - virtual void ready() { - if (_enabled) - _ready = true; + virtual void ready() { + if (_enabled) + _ready = true; } virtual bool is_ready() { @@ -55,6 +55,11 @@ class Device { void enable(bool enabled=true) { _enabled = enabled; _enable(enabled); } bool is_enabled() { return _enabled; } + // calls to turn off device altogether and back on again + virtual void power_module(bool turn_on) { if (turn_on) wake(); else sleep(); }; + virtual void sleep() {}; + virtual void wake() {}; + int id() { return _id; } inline unsigned negotiate_mode(unsigned sys) { @@ -69,7 +74,7 @@ class Device { private: int _id; - bool _enabled; + volatile bool _enabled; volatile bool _ready; }; From bf53e651a071eaeaada8dfcc20f4c5533d0a0473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:33:29 -0800 Subject: [PATCH 06/18] Implement sleep and wake functions for the timer. The timer must be awake before changes can be made to its registers. --- atmega328p/timer.cpp | 47 ++++++++++++++++++++++++++++++++++++++------ attiny84/timer.cpp | 46 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/atmega328p/timer.cpp b/atmega328p/timer.cpp index 9f31151..8e9899f 100644 --- a/atmega328p/timer.cpp +++ b/atmega328p/timer.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -7,32 +8,66 @@ #include "atimer.h" #include "timer.h" +// Timer 1 is used so as to avoid conflict with the timer 0, used by the delay() function in the Arduino library static Device *t1; +static uint16_t count_adjust = 0; // An adjustment amount for the comparison value (alternates between 0 and 1) + +// Ensure that these are defined properly +#define power_timer1_enable() (PRR &= (uint8_t)~(1 << PRTIM1)) +#define power_timer1_disable() (PRR |= (uint8_t)(1 << PRTIM1)) ISR(TIMER1_COMPA_vect) { if (t1) t1->ready(); } +// call to turn off device altogether +void Timer::sleep() +{ + power_timer1_disable(); +} + +// call to turn device on again +void Timer::wake() +{ + power_timer1_enable(); +} + + bool Timer::begin() { t1 = this; + wake(); TCCR1A = 0; // CTC mode, 1024 prescaler TCCR1B = bit(WGM12) | bit(CS10) | bit(CS12); - OCR1A = F_CPU / 1024000 - 1; // 1 ms + OCR1A = (uint16_t) (F_CPU / (1024000L * (uint32_t) _ms_divisor)) - 1; // 1 ms/_ms_divisor approximately + sleep(); return false; } void Timer::_enable(bool e) { - if (e) - TIMSK1 |= bit(OCIE1A); - else - TIMSK1 &= ~bit(OCIE1A); + uint8_t saved_SREG = SREG; // Save the interrupt flag + cli(); // disable interrupts while resetting the timer/counter + TCNT1 = 0; // Reset the timer/counter + SREG = saved_SREG; // restore the interrupt flag + + bitWrite(TIMSK1, OCIE1A, e); } +// Alternate the value of the timer compare value to increase accuracy +// void Timer::ready() { +// count_adjust = 1 - count_adjust; +// uint16_t new_compare_value = F_CPU / (1024000L*_ms_divisor) - count_adjust; +// uint8_t saved_SREG = SREG; // Save the interrupt flag +// cli(); // disable interrupts while changing the compare count +// OCR1A = new_compare_value; +// SREG = saved_SREG; // restore the interrupt flag +// +// AbstractTimer::ready(); +// } + unsigned Timer::_sleepmode() { return SLEEP_MODE_IDLE; } - diff --git a/attiny84/timer.cpp b/attiny84/timer.cpp index 97307dc..1d89205 100644 --- a/attiny84/timer.cpp +++ b/attiny84/timer.cpp @@ -7,31 +7,63 @@ #include "atimer.h" #include "timer.h" +// Timer 1 is used so as to avoid conflict with the timer 0, used by the delay() function in the Arduino library static Device *t1; +// Ensure that these are defined properly +#define power_timer1_enable() (PRR &= (uint8_t)~(1 << PRTIM1)) +#define power_timer1_disable() (PRR |= (uint8_t)(1 << PRTIM1)) + ISR(TIM1_COMPA_vect) { if (t1) t1->ready(); } +// call to turn off device altogether +void Timer::sleep() +{ + power_timer1_disable(); +} + +// call to turn device on again +void Timer::wake() +{ + power_timer1_enable(); +} + + bool Timer::begin() { t1 = this; + wake(); // Timer must be powered on for the register changes to take effect TCCR1A = 0; - // CTC mode, no prescaler - TCCR1B = _BV(WGM12) | _BV(CS10); + // CTC mode, clock stopped + TCCR1B = _BV(WGM12); + + uint8_t saved_SREG = SREG; // Save the interrupt flag + cli(); // disable interrupts while resetting the timer/counter + OCR1A = (uint16_t) (F_CPU / (1000 * (uint32_t) _ms_divisor)) - 1; // 1ms divided by the divisor + SREG = saved_SREG; // restore the interrupt flag + sleep(); - OCR1A = F_CPU / 1000 - 1; // 1ms return false; } +// Timer must be awake before calling this function void Timer::_enable(bool e) { - if (e) - TIMSK1 |= _BV(OCIE1A); - else - TIMSK1 &= ~_BV(OCIE1A); + uint8_t saved_SREG = SREG; // Save the interrupt flag + cli(); // disable interrupts while resetting the timer/counter + TCNT1 = 0; // Reset the timer/counter + bitWrite(TCCR1B, CS10, e); // Start or stop the clock + bitWrite(TIMSK1, OCIE1A, e); + SREG = saved_SREG; // restore the interrupt flag } +// Pass through +// void Timer::ready() { +// AbstractTimer::ready(); +// } + unsigned Timer::_sleepmode() { return SLEEP_MODE_IDLE; } From f76e69ffd1273028b8b831aa7603468d7f8a4fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:36:36 -0800 Subject: [PATCH 07/18] Implement serial output for attiny84. --- attiny84/serial.cpp | 56 ++++++++++++++ attiny84/serialout.cpp | 165 +++++++++++++++++++++++++++++++++++++++++ serial.h | 14 +++- serialout.h | 21 ++++-- 4 files changed, 249 insertions(+), 7 deletions(-) create mode 100644 attiny84/serial.cpp create mode 100644 attiny84/serialout.cpp diff --git a/attiny84/serial.cpp b/attiny84/serial.cpp new file mode 100644 index 0000000..83374b0 --- /dev/null +++ b/attiny84/serial.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include + +#include "device.h" +#include "serial.h" + +#define IOCLK_PRESCALE 256 + +void SerialDevice::init() { + if (_baud) { + unsigned bit_timing_count = ((F_CPU) / IOCLK_PRESCALE + (_baud / 2)) / _baud - 1; + + uint8_t saved_SREG = SREG; // Save the interrupt flag + cli(); // disable interrupts while initializing the timer 0 + + // Timer 0 is an 8 bit timer, and used for the timing of the serial port + TCCR0A = 0; + TCCR0B = 0; + + // Put Timer/Counter0 in CTC mode + bitSet(TCCR0A, WGM01); + + // Count cycles for each bit + OCR0A = bit_timing_count; + + SREG = saved_SREG; // restore the interrupt flag + } +} + +unsigned SerialDevice::_sleepmode() { + return SLEEP_MODE_IDLE; +} + +// call to turn off device altogether +void SerialDevice::sleep() +{ + if (true) //(_awake) + { + power_timer0_disable(); // Timer 0 is used to time the bits + _awake = false; + } +} + +// call to turn device on again +void SerialDevice::wake() +{ + if (true) //(!_awake) + { + power_timer0_enable(); // Timer 0 is used to time the bits + this->init(); // After power on, initialize the module + _awake = true; + } +} diff --git a/attiny84/serialout.cpp b/attiny84/serialout.cpp new file mode 100644 index 0000000..bc4a9d1 --- /dev/null +++ b/attiny84/serialout.cpp @@ -0,0 +1,165 @@ +// Serial output for the ATTiny84 +// This code is derived from the SerialOut class from the atmega328p +// implementation in the Interrupted library and ideas from +// http://electronut.in/serial-communications-with-the-attiny84/ + +#include +#include +#include +#include +#include + +#include "device.h" +#include "serial.h" +#include "serialout.h" + +#define TRANSMISSION_SIZE 8 // not including 1 start bit + 1 stop bit +// #define TRANSMISSION_SIZE 10 // Add 2 idle bits but not including 1 start bit + 1 stop bit + +volatile unsigned char current_byte = 0; +volatile int next_bit_index = -1; // Index of next bit to send; -1 = not sending data yet + +static SerialOut *device; + +void send_bit_using_pin(unsigned int pinTX) +{ + // serial data sent least significant bit first + // start(low)-0-1-2-3-4-5-6-7-stop(high) + // 10 bits sent per packet + + if (next_bit_index < 0) + { + // start bit is low + bitClear(PORTA, pinTX); + + // set bit index for first bit + next_bit_index = 0; + } + else if (next_bit_index >= TRANSMISSION_SIZE) + { + // idle or stop bit is high + bitSet(PORTA, pinTX); + next_bit_index = -1; + } + #if TRANSMISSION_SIZE > 8 + else if (next_bit_index >= 8) + { + // Tranmission frame has idle bits, idle and stop bits are high + bitSet(PORTA, pinTX); + next_bit_index++; + } + #endif + else + { + // data bits: + // next_bit_index is in [0, 7] + // extract relevant bit from data + bitWrite(PORTA, pinTX, bitRead(current_byte, next_bit_index)); + next_bit_index++; + } + +} + +bool SerialOut::begin() { + device = this; + _awake = false; + + _next_char_index = -1; + + // use the id as the TX pin, set the idle line state to high + digitalWrite(this->id(), HIGH); + pinMode(this->id(), OUTPUT); + + return false; +} + +void SerialOut::_enable(bool e) { + bitSet(TIFR0, OCF0A); // Ensure the interrupt flag is clear to start + bitWrite(TIMSK0, OCIE0A, e); +} + +void SerialOut::start(char const *ptr, int buffer_offset) +{ + if (_next_char_index < 0) + { + // Not currently transmitting, so start one + _next_char_index = buffer_offset; + wake(); + enable(); + write(ptr); + } +} + +// Prepare for transmission of characters from the given buffer +void SerialOut::write(char const *ptr) { + if (is_enabled()) + { + uint8_t saved_status = SREG; + cli(); + _tx_ptr = ptr; + bitSet(TIFR0, OCF0A); // Ensure the interrupt flag is clear to start + + TCNT0 = 0; // Reset the timer + bitSet(TCCR0B, CS02); // Set the clock prescale bit for divide by 256 to start the clock + + next_bit_index = -1; // Indicate that a new character is to be started + do_output(); + SREG = saved_status; + } +} + +const char SerialOut::more() +{ + return _tx_ptr && _next_char_index >= 0 && _tx_ptr[_next_char_index]; +} + +char SerialOut::next() +{ + char next_char = _tx_ptr[_next_char_index]; + this->advance_to_next_character(); + return next_char; +} + +void SerialOut::advance_to_next_character() +{ + _next_char_index++; +} + +void SerialOut::finished() +{ + _tx_ptr = 0; + _next_char_index = -1; +} + +// Output a single bit of a transmission frame; may called from interrupt service routine +// If no more characters, then signal that the device is ready +void SerialOut::do_output() +{ + if (next_bit_index >= 0) + { + // Handle the transmission of the next bit + send_bit_using_pin(this->id()); + } + else + { + // Check to see if the current null terminated string of bytes has been sent + if (more()) + { + current_byte = next(); + send_bit_using_pin(this->id()); + } + else + { + finished(); + bitClear(TCCR0B, CS02); // stop the clock for the timer + ready(); + } + } +} + +// Output the next bit of the current byte +ISR(TIM0_COMPA_vect) +{ + if (device) + device->do_output(); +} diff --git a/serial.h b/serial.h index 4d62880..2b38586 100644 --- a/serial.h +++ b/serial.h @@ -1,12 +1,22 @@ #ifndef __SERIALDEVICE_H__ #define __SERIALDEVICE_H__ +#include "device.h" + class SerialDevice: public Device { public: - SerialDevice(unsigned id, unsigned long baud): - Device(id), _baud(baud) {} + SerialDevice(unsigned id, unsigned long baud): + Device(id), _awake(false), _baud(baud) {} void init(); + virtual void sleep(); + virtual void wake(); + + virtual bool is_awake() {return _awake; } + +protected: + bool _awake; + private: unsigned _sleepmode(); unsigned long _baud; diff --git a/serialout.h b/serialout.h index d5c02ab..c00d61c 100644 --- a/serialout.h +++ b/serialout.h @@ -1,26 +1,37 @@ #ifndef __SERIALOUT_H__ #define __SERIALOUT_H__ +#include "serial.h" + /** * Simple Serial output device */ class SerialOut: public SerialDevice { public: - SerialOut(unsigned id, unsigned long baud = 0): - SerialDevice(id, baud), _tx_ptr(0) {} + SerialOut(unsigned id, unsigned long baud = 0): + SerialDevice(id, baud), _tx_ptr(0), _next_char_index(-1) {} // not enabled by default bool begin(); // writes a string void write(char const *ptr); - void do_output(); + virtual void do_output(); + + // Return true when there are still bytes to output + bool transmitting() {return _tx_ptr != 0; } + virtual void start(char const *ptr, int offset=0); + virtual const char more(); + virtual char next(); + virtual void advance_to_next_character(); + virtual void finished(); protected: - void _enable(bool) {} + void _enable(bool e); + volatile char const *_tx_ptr; + volatile int _next_char_index; // Index of next byte to output private: - volatile char const *_tx_ptr; }; #endif From 27a7f3430aff0aec37fd08fb0d87d0e1e8d0a27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:37:22 -0800 Subject: [PATCH 08/18] Support sleep and wake functions for the serial port for atmega328p --- atmega328p/serial.cpp | 22 +++++++++++++++++++--- atmega328p/serialin.cpp | 8 ++------ atmega328p/serialout.cpp | 36 ++++++++++++++++++++++++++++++------ 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/atmega328p/serial.cpp b/atmega328p/serial.cpp index fc720fe..04adba8 100644 --- a/atmega328p/serial.cpp +++ b/atmega328p/serial.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -8,15 +9,30 @@ void SerialDevice::init() { if (_baud) { - cli(); unsigned prescale = ((F_CPU) / 16 + (_baud / 2)) / _baud - 1; + + uint8_t saved_SREG = SREG; // Save the interrupt flag + cli(); // disable interrupts while initializing the USART UBRR0H = (prescale >> 8) & 0xff; UBRR0L = prescale & 0xff; - UCSR0C = bit(UCSZ00) | bit(UCSZ01); - sei(); + UCSR0C = bit(UCSZ00) | bit(UCSZ01); // 8 bits, no parity, 1 stop bit + SREG = saved_SREG; // restore the interrupt flag } } unsigned SerialDevice::_sleepmode() { return SLEEP_MODE_IDLE; } + +// call to turn off device altogether +void SerialDevice::sleep() +{ + power_usart0_disable(); // Power down the USART +} + +// call to turn device on again +void SerialDevice::wake() +{ + power_usart0_enable(); // Ensure USART powered up + this->init(); // After power on, the module must be initialized +} diff --git a/atmega328p/serialin.cpp b/atmega328p/serialin.cpp index 159bd3e..aa6a078 100644 --- a/atmega328p/serialin.cpp +++ b/atmega328p/serialin.cpp @@ -11,16 +11,12 @@ static SerialIn *device; bool SerialIn::begin() { device = this; - init(); - UCSR0B |= bit(RXEN0); return true; } void SerialIn::_enable(bool e) { - if (e) - UCSR0B |= bit(RXCIE0); - else - UCSR0B &= ~bit(RXCIE0); + bitWrite(UCSR0B, RXEN0, e); + bitWrite(UCSR0B, RXCIE0, e); } ISR(USART_RX_vect) diff --git a/atmega328p/serialout.cpp b/atmega328p/serialout.cpp index bc73424..06d6b5e 100644 --- a/atmega328p/serialout.cpp +++ b/atmega328p/serialout.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -11,34 +12,57 @@ static SerialOut *device; bool SerialOut::begin() { device = this; - init(); - UCSR0B |= bit(TXEN0); return false; } -void SerialOut::write(char const *ptr) { +void SerialOut::_enable(bool e) { + bitClear(UCSR0B, TXCIE0); + bitWrite(UCSR0B, TXEN0, e); +} + +// Prepare for transmission of characters from the given buffer +void SerialOut::write(char const *ptr) { if (is_enabled() && !_tx_ptr) { + uint8_t saved_status = SREG; cli(); _tx_ptr = ptr; - UCSR0B |= bit(UDRIE0); - sei(); + bitSet(UCSR0B, UDRIE0); + bitSet(UCSR0B, TXCIE0); + SREG = saved_status; } } +// Output a single character; may be called from interrupt service routine +// If no more characters, then signal that the device is ready void SerialOut::do_output() { byte b = *_tx_ptr; if (b) { _tx_ptr++; + bitClear(UCSR0B, TXC0); UDR0 = b; } else { _tx_ptr = 0; UCSR0B &= ~bit(UDRIE0); - ready(); + if (bitRead(UCSR0B, TXC0)) + { + bitClear(UCSR0B, TXCIE0); // Don't need this interrupt now - the transmission finished while in this routine + ready(); + } } } +// When the transmitter data register is empty, output the next byte ISR(USART_UDRE_vect) { if (device) device->do_output(); } + +// When the transmission is complete, the device is ready +ISR(USART_TXC_vect) +{ + if (device && !device->transmitting()) + { + device->ready(); + } +} From c9826ebaf46e34b78b040b64785bccfb3ba6918c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:38:31 -0800 Subject: [PATCH 09/18] Change AbstractTimer to not depend on Arduino header. --- atimer.h | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/atimer.h b/atimer.h index 87e76dd..5e24140 100644 --- a/atimer.h +++ b/atimer.h @@ -1,27 +1,36 @@ #ifndef __ATIMER_H__ #define __ATIMER_H__ +// #include +#include +#include +#include "device.h" + /** - * An abstract one-shot timer. + * An abstract one-shot timer. */ class AbstractTimer: public Device { public: void ready() { - if (--_ticks == 0) { + if (_ticks <= 1) { Device::ready(); _ticks = _delay; disable(); } + else + { + --_ticks; + } } - void delay(uint32_t d) { - noInterrupts(); - _ticks = _delay = d; - interrupts(); + void delay(uint32_t d) { + cli(); + _ticks = _delay = d; + sei(); } protected: - AbstractTimer(int id, uint32_t delay): + AbstractTimer(int id, uint32_t delay): Device(id), _delay(delay), _ticks(delay) {} private: From 0499c7abadb38f652cfb558203f3cfce84af30fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:39:54 -0800 Subject: [PATCH 10/18] Documentation added; reset watchdog timer when enabling. --- atmega328p/watchdog.cpp | 12 ++++++------ attiny84/watchdog.cpp | 29 ++++++++++++++++------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/atmega328p/watchdog.cpp b/atmega328p/watchdog.cpp index 01984b6..d29227f 100644 --- a/atmega328p/watchdog.cpp +++ b/atmega328p/watchdog.cpp @@ -9,7 +9,7 @@ static Device *wdt; -ISR(WDT_vect) +ISR(WDT_vect) { if (wdt) wdt->ready(); @@ -53,7 +53,7 @@ void Watchdog::_prescale() { cli(); MCUSR &= ~_BV(WDRF); WDTCSR |= _BV(WDCE) | _BV(WDE); // change prescaler - WDTCSR = prescale; + WDTCSR = prescale; // WDE bit is zero here as well sei(); } @@ -64,10 +64,10 @@ bool Watchdog::begin() { } void Watchdog::_enable(bool e) { - if (e) - WDTCSR |= _BV(WDIE); - else - WDTCSR &= ~_BV(WDIE); + wdt_reset(); // Ensure that the timer will start from 0 again + + // [en|dis]able watchdog interrupts + bitWrite(WDTCSR, WDIE, e); } unsigned Watchdog::_sleepmode() { diff --git a/attiny84/watchdog.cpp b/attiny84/watchdog.cpp index dfe3ecb..2729202 100644 --- a/attiny84/watchdog.cpp +++ b/attiny84/watchdog.cpp @@ -9,28 +9,26 @@ static Device *wdt; -ISR(WDT_vect) +ISR(WDT_vect) { if (wdt) wdt->ready(); } // http://donalmorrissey.blogspot.ie/2010/04/sleeping-arduino-part-5-wake-up-via.html -bool Watchdog::begin() { - wdt = this; - +void Watchdog::_prescale() { unsigned prescale = 0; switch (_scale) { - case WDTO_15MS: + case WDTO_15MS: // Actually 16 ms break; case WDTO_30MS: - prescale = _BV(WDP0); + prescale = _BV(WDP0); // Actually 32 ms break; case WDTO_60MS: - prescale = _BV(WDP1); + prescale = _BV(WDP1); // Actually 64 ms break; case WDTO_120MS: - prescale = _BV(WDP1) | _BV(WDP0); + prescale = _BV(WDP1) | _BV(WDP0); // Actually 125 ms break; case WDTO_250MS: prescale = _BV(WDP2); @@ -55,20 +53,25 @@ bool Watchdog::begin() { cli(); MCUSR &= ~_BV(WDRF); - WDTCSR |= _BV(WDCE) | _BV(WDE) | _BV(WDIF); // change prescaler - WDTCSR = prescale; + WDTCSR |= _BV(WDCE) | _BV(WDE) | _BV(WDIF); + WDTCSR = prescale; // change prescaler, also setting WDE bit to 0 sei(); +} + +bool Watchdog::begin() { + wdt = this; + _prescale(); return false; } void Watchdog::_enable(bool e) { + wdt_reset(); // Ensure that the timer will start from 0 again if (e) - WDTCSR |= _BV(WDIE); + WDTCSR |= _BV(WDIE); // Enable the interrupt without changing the prescale value else - WDTCSR &= ~_BV(WDIE); + WDTCSR &= ~_BV(WDIE); // Since WDE is zero, the timer is stopped } unsigned Watchdog::_sleepmode() { return SLEEP_MODE_PWR_DOWN; } - From 42b731722ea856d91c36a9fae940b68708cdcd32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:41:28 -0800 Subject: [PATCH 11/18] Change Timer interface to include sleep and wake functions. Also, allow a millisecond divisor to be specified explicitly. --- timer.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/timer.h b/timer.h index a713d9c..9e75152 100644 --- a/timer.h +++ b/timer.h @@ -1,19 +1,28 @@ #ifndef __TIMER_H__ #define __TIMER_H__ +#include "atimer.h" + /** * A millisecond timer. */ class Timer: public AbstractTimer { public: - Timer(int id, uint32_t millis): AbstractTimer(id, millis) {} + Timer(int id, uint32_t millis, uint32_t ms_divisor=1L): + AbstractTimer(id, millis), _ms_divisor(ms_divisor) {} // not enabled by default bool begin(); + // virtual void ready(); + + virtual void sleep(); + virtual void wake(); + protected: void _enable(bool); unsigned _sleepmode(); + uint32_t _ms_divisor; }; #endif From 29c0602614a53477db9e7d7c6aafc426b186eb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:41:57 -0800 Subject: [PATCH 12/18] Ignore some files used by PlatformIO --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 1c90c38..2ef7e4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.pioenvs +.clang_complete +.gcc-flags.json *.bin *.eep *.elf @@ -8,3 +11,4 @@ .*.d libcore.a .*.swp +.piolibdeps \ No newline at end of file From d58d3f31bb17aa02182bf541fabb14ec9a6f5511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:42:32 -0800 Subject: [PATCH 13/18] Add a PlatformIO library description file. --- .library.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .library.json diff --git a/.library.json b/.library.json new file mode 100644 index 0000000..2807c9a --- /dev/null +++ b/.library.json @@ -0,0 +1,21 @@ +{ + "name": "Interrupted", + "authors": + [ + { + "name": "Stephen Crane", + "email": "jscrane@gmail.com", + "url": "https://www.linkedin.com/in/jscrane" + } + ], + "version" : "1.1.0", + "description" : "Interrupt driven device framework", + "keywords" : "avr, Arduino", + "repository": + { + "type" : "git", + "url": "file:///Users/icaven/Documents/Machining/Watchwinder/software/lib/Interrupted" + }, + "frameworks": ["arduino"], + "platforms": ["atmelavr"] +} From 9204605b5222f0d3946847a1e394cc6c21fe2134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 10:43:31 -0800 Subject: [PATCH 14/18] Add PlatformIO configuration files. --- .travis.yml | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ platformio.ini | 20 ++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 .travis.yml create mode 100644 platformio.ini diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..72c6e43 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,65 @@ +# Continuous Integration (CI) is the practice, in software +# engineering, of merging all developer working copies with a shared mainline +# several times a day < http://docs.platformio.org/en/stable/ci/index.html > +# +# Documentation: +# +# * Travis CI Embedded Builds with PlatformIO +# < https://docs.travis-ci.com/user/integration/platformio/ > +# +# * PlatformIO integration with Travis CI +# < http://docs.platformio.org/en/stable/ci/travis.html > +# +# * User Guide for `platformio ci` command +# < http://docs.platformio.org/en/stable/userguide/cmd_ci.html > +# +# +# Please choice one of the following templates (proposed below) and uncomment +# it (remove "# " before each line) or use own configuration according to the +# Travis CI documentation (see above). +# + + +# +# Template #1: General project. Test it using existing `platformio.ini`. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# install: +# - pip install -U platformio +# +# script: +# - platformio run + + +# +# Template #2: The project is intended to by used as a library with examples +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# env: +# - PLATFORMIO_CI_SRC=path/to/test/file.c +# - PLATFORMIO_CI_SRC=examples/file.ino +# - PLATFORMIO_CI_SRC=path/to/test/directory +# +# install: +# - pip install -U platformio +# +# script: +# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..98ecbdd --- /dev/null +++ b/platformio.ini @@ -0,0 +1,20 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter, extra scripting +; Upload options: custom port, speed and extra flags +; Library options: dependencies, extra library storages +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/en/stable/projectconf.html + +[env:attiny84] +platform = atmelavr +board = attiny84 +framework = arduino +src_filter = ""-<*> +"" + +[env:diecimilaatmega328] +platform = atmelavr +board = diecimilaatmega328 +framework = arduino +src_filter = ""-<*> +"" From 9f54136b74633b01349c48cef27e9f114118e967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 12:00:30 -0800 Subject: [PATCH 15/18] Add the maintainer flag to the library manifest. --- .library.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.library.json b/.library.json index 2807c9a..1c6d0b7 100644 --- a/.library.json +++ b/.library.json @@ -5,7 +5,8 @@ { "name": "Stephen Crane", "email": "jscrane@gmail.com", - "url": "https://www.linkedin.com/in/jscrane" + "url": "https://www.linkedin.com/in/jscrane", + "maintainer": true } ], "version" : "1.1.0", From fa0faf5d54226a63085348127fd66cd63e60c21a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 14:12:04 -0800 Subject: [PATCH 16/18] Merge changes with most recent pull from origin. --- atmega328p/timer.cpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/atmega328p/timer.cpp b/atmega328p/timer.cpp index 379ac27..9389869 100644 --- a/atmega328p/timer.cpp +++ b/atmega328p/timer.cpp @@ -9,11 +9,14 @@ // Timer 1 is used so as to avoid conflict with the timer 0, used by the delay() function in the Arduino library static Device *t1; -static uint16_t count_adjust = 0; // An adjustment amount for the comparison value (alternates between 0 and 1) // Ensure that these are defined properly +#ifndef power_timer1_enable #define power_timer1_enable() (PRR &= (uint8_t)~(1 << PRTIM1)) +#endif +#ifndef power_timer1_disable #define power_timer1_disable() (PRR |= (uint8_t)(1 << PRTIM1)) +#endif ISR(TIMER1_COMPA_vect) { if (t1) @@ -41,7 +44,7 @@ bool Timer::begin() { // CTC mode, 1024 prescaler TCCR1B = bit(WGM12) | bit(CS10) | bit(CS12); - OCR1A = (uint16_t) (F_CPU / (1024000L * (uint32_t) _ms_divisor)) - 1; // 1 ms/_ms_divisor approximately + OCR1A = (uint16_t) (F_CPU / (1024000L * (uint32_t) _ms_divisor)) - 1; // approximately 1 ms divided by the divisor sleep(); return false; } @@ -55,18 +58,6 @@ void Timer::_enable(bool e) { bitWrite(TIMSK1, OCIE1A, e); } -// Alternate the value of the timer compare value to increase accuracy -// void Timer::ready() { -// count_adjust = 1 - count_adjust; -// uint16_t new_compare_value = F_CPU / (1024000L*_ms_divisor) - count_adjust; -// uint8_t saved_SREG = SREG; // Save the interrupt flag -// cli(); // disable interrupts while changing the compare count -// OCR1A = new_compare_value; -// SREG = saved_SREG; // restore the interrupt flag -// -// AbstractTimer::ready(); -// } - unsigned Timer::_sleepmode() { return SLEEP_MODE_IDLE; } From a00cdc6fc1ee24cae271a7311230b0f440f8d6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Wed, 11 Jan 2017 14:12:36 -0800 Subject: [PATCH 17/18] Ensure that the timer device is selected. --- examples/attiny84/timer.ino | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/attiny84/timer.ino b/examples/attiny84/timer.ino index 781a3a1..0087c9c 100644 --- a/examples/attiny84/timer.ino +++ b/examples/attiny84/timer.ino @@ -19,8 +19,10 @@ void setup(void) void loop(void) { timer.enable(); - devices.select(); - digitalWrite(LED, !digitalRead(LED)); - dt *= 2; - timer.delay(dt); + if (devices.select() == 1) + { + digitalWrite(LED, !digitalRead(LED)); + dt *= 2; + timer.delay(dt); + } } From 71d5de20f16ec133eb7953b4be3b9c1313ff0038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ian=20Cave=CC=81n?= Date: Mon, 16 Jan 2017 08:07:22 -0800 Subject: [PATCH 18/18] Correct the url to the library. --- .library.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.library.json b/.library.json index 1c6d0b7..b635827 100644 --- a/.library.json +++ b/.library.json @@ -15,7 +15,7 @@ "repository": { "type" : "git", - "url": "file:///Users/icaven/Documents/Machining/Watchwinder/software/lib/Interrupted" + "url": "https://github.com/jscrane/Interrupted.git" }, "frameworks": ["arduino"], "platforms": ["atmelavr"]