From 9fd122c5708dff7ce70dfffcf5afe0520659250c Mon Sep 17 00:00:00 2001 From: Pat Thoyts Date: Sat, 20 Feb 2016 13:06:09 +0000 Subject: [PATCH] ATtiny85 PWM demo --- .gitignore | 3 + Makefile | 77 +++++++++++++++++++ README | 12 +++ pwm_demo.c | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 306 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README create mode 100644 pwm_demo.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c41c150 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.hex +*.elf +*.o diff --git a/Makefile b/Makefile new file mode 100644 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 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 index 0000000..eb0fc0a --- /dev/null +++ b/pwm_demo.c @@ -0,0 +1,214 @@ +/* pwm_demo - Copyright (C) 2016 Pat Thoyts + * + * 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 +#include +#include +#include +#include +#include +#include + +/** + * 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 */ + } + } +} -- 2.23.0