+/* pwm_demo - Copyright (C) 2016 Pat Thoyts <patthoyts@users.sourceforge.net>
+ *
+ * Demonstrate configuration of PWM on ATtiny85 to control 3 or 4 pins.
+ * Timer0 is controlling PB0 and PB1 with a 495 Hz frequency variable duty
+ * Timer1 is conrolling PB3 and PB4 with a 1kHz frequency.
+ * the overflow interrupt from the 1kHz timer is used to control timing
+ * for a heartbeat indicator on PB2 and the interval between changing the
+ * duty cycle to obtain a 'breathe' pulse pattern.
+ * PB3 is the inverse of PB4 - these two are tied together.
+ * PB0, PB1 and PB3/4are independently controllable
+ */
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/pgmspace.h>
+#include <avr/power.h>
+#include <avr/sleep.h>
+#include <avr/wdt.h>
+#include <util/delay.h>
+
+/**
+ * Timer interval handling
+ *
+ * We have a 1kHz interrupt being generated from timer 1.
+ * To handle various polling operations we use the following
+ * functions to setup match values as a specific interval in
+ * the future (up to 65535 ms only) and check the flag to see
+ * if the time has passed.
+ *
+ * API is timer_poll() and timer_set() and millis().
+ */
+
+typedef enum Timer { Timer_Pulse, Timer_Heartbeat } Timer;
+struct TimerState
+{
+ uint16_t match;
+ uint8_t flag;
+};
+volatile struct TimerState _timers[2] = {{0, 0}, {0, 0}};
+volatile uint16_t _millis = 0;
+
+ISR(TIMER1_OVF_vect)
+{
+ /* called on timer1 overflow at 1kHz */
+ ++_millis;
+ for (int n = 0; n < sizeof(_timers)/sizeof(_timers[0]); ++n)
+ {
+ if (_millis == _timers[n].match)
+ _timers[n].flag = 1;
+ }
+}
+
+inline uint16_t millis() { return _millis; }
+
+/**
+ * Test if the specified timer interval has passed
+ * This must be reset by calling timer_set() or timer_reset().
+ */
+inline int timer_poll(Timer timer) { return _timers[timer].flag; }
+
+/**
+ * Set the specified timer to be signalled afer a number of milliseconds
+ * have elapsed.
+ * @param match should be the number of milliseconds to elapse
+ */
+static void timer_set(Timer timer, uint16_t interval)
+{
+ _timers[timer].match = millis() + interval;
+ _timers[timer].flag = 0;
+}
+
+/* Pulse pattern generated using:
+ * x=[0:(4000/255):4000];
+ * y=((exp(sin(x/2000*pi)) - 0.36787944) * 108.0);
+ * y=int32(y')
+ */
+const uint8_t Pattern[255] PROGMEM =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3,
+ 4, 4, 5, 5, 6, 7, 7, 8, 9, 9, 10, 11, 12, 13, 13, 14, 15,
+ 16, 17, 19, 20, 21, 22, 23, 25, 26, 28, 29, 31, 32, 34,
+ 36, 37, 39, 41, 43, 45, 47, 49, 51, 53, 56, 58, 61, 63, 66,
+ 68, 71, 74, 77, 79, 82, 85, 88, 92, 95, 98, 101, 105, 108, 112,
+ 115, 119, 122, 126, 130, 134, 137, 141, 145, 149, 153, 157, 160,
+ 164, 168, 172, 176, 180, 184, 187, 191, 195, 198, 202, 205, 209,
+ 212, 215, 219, 222, 225, 227, 230, 233, 235, 238, 240, 242, 244,
+ 246, 247, 249, 250, 251, 252, 253, 253, 254, 254, 254, 254, 253,
+ 253, 252, 251, 250, 249, 248, 246, 245, 243, 241, 239, 236, 234,
+ 232, 229, 226, 223, 220, 217, 214, 211, 207, 204, 200, 197, 193,
+ 189, 185, 182, 178, 174, 170, 166, 162, 159, 155, 151, 147, 143,
+ 139, 135, 132, 128, 124, 121, 117, 113, 110, 107, 103, 100, 96,
+ 93, 90, 87, 84, 81, 78, 75, 72, 70, 67, 64, 62, 59, 57, 55, 52,
+ 50, 48, 46, 44, 42, 40, 38, 36, 35, 33, 31, 30, 28, 27, 25, 24,
+ 23, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 10, 9, 8, 8,
+ 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0
+};
+
+/* put the processor into the currently defined sleep mode */
+inline void sleep_now(void)
+{
+ cli();
+ sleep_enable();
+ sei();
+ sleep_cpu();
+ sleep_disable();
+}
+
+/* Configure fast PWM on PB0 and PB1 (OC0A, OC0B) */
+static void
+init_pwm_timer0()
+{
+ cli();
+ /* set OC0A and OC0B as output and low */
+ DDRB |= _BV(PB0) | _BV(PB1);
+ PORTB &= ~(_BV(PB0) | _BV(PB1));
+ /* WGM = 0b011: Fast PWM: TOP=0xFF, update at BOTTOM
+ * COM0A = 0b11: set OC0A on compare match (BOTTOM)
+ * COM0A = 0b11: set OC0A on compare match (BOTTOM)
+ * CS = 0b011: CLK/64
+ *
+ * 8e6 / 64 = 125 kHz
+ * 125e3 / 255 = 490 Hz (actual is 496Hz so /252 ?)
+ */
+ TCCR0A =
+ _BV(COM0A1) | _BV(COM0A0) /* set OC0A on compare match */
+ | _BV(COM0B1) | _BV(COM0B0) /* set OC0B on compare match */
+ | _BV(WGM01) | _BV(WGM00); /* Fast PWM mode */
+ TCCR0B = _BV(CS01) | _BV(CS00);
+ /* control the duty by varying the compare match registers */
+ OCR0A = 0;
+ OCR0B = 0;
+ sei();
+}
+
+/* Configure fast PWM on PB3 (OC1B) */
+static void
+init_pwm_timer1()
+{
+ cli();
+ /* set pin PB1 (OC1A) as output */
+ DDRB |= _BV(DDB3) | _BV(DDB4);
+ PORTB &= ~_BV(PB3) | _BV(PB4);
+ /* COM1A = 0b01: OC1A disconnected
+ * COM1B = 0b11: OC1B set on compare match, clear at BOTTOM, OC1B# disabled
+ * COM1B = 0b01: OC1B clear on compare match, set at BOTTOM, OC1B# enabled
+ * PWM1A = 0: disabled modulator A (attached to PB0 and PB1)
+ * PWM1B = 1: enable PWM mode for OCR1B and 0 when match OCR1C
+ * CS = 0b0111: PCK/64
+ * OCR1C = 0xFF: TOP, resets counter when matches
+ * freq = (8MHz / 64 / OCR1C) -> 1kHz for OCR1C==125.
+ */
+ TCCR1 = _BV(COM1A0) | _BV(CS12) | _BV(CS11) | _BV(CS10);
+ GTCCR = _BV(PWM1B) | /*_BV(COM1B1) |*/ _BV(COM1B0);
+ OCR1B = 0;
+ OCR1C = 125;
+
+ TIMSK |= _BV(TOIE1); /* enable timer1 overflow interrupt */
+ sei();
+}
+
+int
+main(void)
+{
+ int index = 0;
+
+ wdt_enable(WDTO_1S);
+
+ power_adc_disable();
+ power_usi_disable();
+
+ /* set all unused pins high-z all outputs LOW */
+ uint8_t outputs = _BV(PB2);
+ DDRB = outputs;
+ PORTB = ~(outputs);
+
+ for (int n = 0; n < 20; ++n)
+ {
+ PORTB ^= _BV(PB2);
+ _delay_ms(50);
+ }
+
+ init_pwm_timer0();
+ init_pwm_timer1();
+
+ timer_set(Timer_Pulse, 10);
+ timer_set(Timer_Heartbeat, 500);
+
+ set_sleep_mode(SLEEP_MODE_IDLE);
+ sei();
+
+ for (;;)
+ {
+ if (timer_poll(Timer_Pulse))
+ {
+ timer_set(Timer_Pulse, 10);
+ uint8_t level = pgm_read_byte_near(Pattern + index);
+ OCR0A = level;
+ OCR0B = 255-level;
+ OCR1B = ((int)OCR1C * (int)level) / 255; /* scale values */
+ if (++index > 255)
+ index = 0;
+ }
+
+ /* sleep as much as possible. gets woken by the timer ovf interrupt */
+ wdt_reset();
+ sleep_now();
+
+ if (timer_poll(Timer_Heartbeat))
+ {
+ timer_set(Timer_Heartbeat, 500);
+ PORTB ^= _BV(PB2); /* flash heartbeat led */
+ }
+ }
+}