ATtiny85 PWM demo master
authorPat Thoyts <patthoyts@users.sourceforge.net>
Sat, 20 Feb 2016 13:06:09 +0000 (13:06 +0000)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Sat, 20 Feb 2016 13:06:09 +0000 (13:06 +0000)
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
pwm_demo.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..c41c150
--- /dev/null
@@ -0,0 +1,3 @@
+*.hex
+*.elf
+*.o
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..bd4fd76
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,77 @@
+# -*- Makefile -*-
+
+PROJECT := pwm_demo
+DEVICE  := attiny85
+F_CPU   := 8000000UL
+INC     := -I.
+#AVRDUDE := avrdude -c usbasp -p $(DEVICE)
+AVRDUDE := avrdude -c atmelice_isp -p $(DEVICE)
+
+CSRCS    = $(PROJECT).c
+OBJS     = $(CSRCS:.c=.o)
+
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+
+CC      := avr-gcc
+LD      := avr-gcc
+OBJCOPY := avr-objcopy
+ifeq ($(uname_S),Linux)
+RM      := rm -f
+else
+RM      := del >NUL
+endif
+CFLAGS  :=-Wall -Wcast-align -Wshadow \
+          -std=gnu99 -fshort-enums -pedantic-errors -Os -mcall-prologues \
+          -mmcu=$(DEVICE) -DF_CPU=$(F_CPU)
+LDFLAGS := -Wall -mmcu=$(DEVICE)# -Wl,-v
+LIBS    :=
+
+V := @
+Q := $(V:1=)
+QUIET_CC      = $(Q:@=@echo   CC      $@ &)$(CC)
+QUIET_LD      = $(Q:@=@echo   LD      $@ &)$(LD)
+QUIET_OBJCOPY = $(Q:@=@echo   OBJCOPY $@ &)$(OBJCOPY)
+QUIET_AVRDUDE = $(Q:@=@echo   AVRDUDE $@ &)$(AVRDUDE)
+
+all: $(PROJECT).hex
+
+%.hex: %.elf
+       $(QUIET_OBJCOPY) -j .text -j .data -O ihex $< $@
+
+%.elf: $(OBJS)
+       $(QUIET_LD) $(LDFLAGS) $^ $(LIBS) -o $@
+
+%.o: %.c
+       $(QUIET_CC) $(CFLAGS) $(INC) -c $<
+
+flash: $(PROJECT).hex
+       $(QUIET_AVRDUDE) -U flash:w:$<:i
+
+clean:
+       -@$(RM) $(addprefix $(PROJECT), .elf .hex .net .bom .cmd)
+
+.PHONY: clean
+.SECONDARY: $(addsuffix .elf, $(PROJECT)) $(OBJS)
+
+# Fuse high byte:
+# 0xdd = 1 1 0 1   1 1 0 1
+#        ^ ^ ^ ^   ^ \-+-/
+#        | | | |   |   +------ BODLEVEL 2..0 (brownout trigger level -> 2.7V)
+#        | | | |   +---------- EESAVE (preserve EEPROM on Chip Erase -> not preserved)
+#        | | | +-------------- WDTON (watchdog timer always on -> disable)
+#        | | +---------------- SPIEN (enable serial programming -> enabled)
+#        | +------------------ DWEN (debug wire enable)
+#        +-------------------- RSTDISBL (disable external reset -> enabled)
+#
+# Fuse low byte:
+# 0xe1 = 1 1 1 0   0 0 0 1
+#        ^ ^ \+/   \--+--/
+#        | |  |       +------- CKSEL 3..0 (clock selection -> HF PLL)
+#        | |  +--------------- SUT 1..0 (BOD enabled, fast rising power)
+#        | +------------------ CKOUT (clock output on CKOUT pin -> disabled)
+#        +-------------------- CKDIV8 (divide clock by 8 -> don't divide)
+# For 16.5MHz internal: -U hfuse:w:0xdd:m -U lfuse:w:0xe1:m
+# For 12MHz crystal: -U hfuse:w:0xdd:m -U lfuse:w:0b11111111:m
+# For 8MHz internal: -U hfuse:w:0xd7:m -U lfuse:w:0xe2:m
+fuse:
+       $(AVRDUDE) -e -U hfuse:w:0xd7:m -U lfuse:w:0xe2:m
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..9e4b93c
--- /dev/null
+++ b/README
@@ -0,0 +1,12 @@
+PWM Demo
+
+Demonstrate PWM control on 4 pins of an ATtiny85.
+
+  RST#  / PB5 1 +----+ 8 VCC
+  OC1B# / PB3 2 |    | 7 PB2
+  OC1B  / PB4 3 |    | 6 PB1 / OC0B / OC1A
+          GND 4 +----+ 5 PB0 / OC0A / OC1A#
+
+Each pin PB0..PB4 connected to an LED with 470R resistor to ground.
+Generates a pulse pattern on each LED by varying the duty cycle
+according to a generated pattern.
diff --git a/pwm_demo.c b/pwm_demo.c
new file mode 100644 (file)
index 0000000..eb0fc0a
--- /dev/null
@@ -0,0 +1,214 @@
+/* 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 */
+        }
+    }
+}