Demo configuration of AVR timer and PWM.
authorPat Thoyts <patthoyts@users.sourceforge.net>
Wed, 12 Aug 2015 11:16:49 +0000 (12:16 +0100)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Wed, 12 Aug 2015 11:16:49 +0000 (12:16 +0100)
Demonostrates the initialization of a timer for 1ms resolution interrupts
and also shows the control of PWM frequency using timer 1.

Signed-off-by: Pat Thoyts <patthoyts@users.sourceforge.net>
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
timer-demo.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..d6c2e45
--- /dev/null
@@ -0,0 +1,2 @@
+*.elf
+*.hex
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..1c0410f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,27 @@
+# -*- Makefile -*-
+
+PROJECT=timer-demo
+DEVICE =atmega328p
+F_CPU  =16000000UL
+INC    =-I.
+AVRDUDE=avrdude -c usbasp -p $(DEVICE) -C $(AVR_DIR)\etc\avrdude.conf
+
+CC=avr-gcc
+LD=avr-gcc
+OBJCOPY=avr-objcopy
+CFLAGS =-Wall -Os -mcall-prologues -mmcu=$(DEVICE) -DF_CPU=$(F_CPU)
+LDFLAGS=-Wall -mmcu=$(DEVICE)
+
+all: $(PROJECT).hex
+
+%.hex: %.elf
+       $(OBJCOPY) -j .text -j .data -O ihex $< $@
+    
+%.elf: %.o
+       $(LD) $(LDFLAGS) $< -o $@
+    
+%.o: %.c
+       $(CC) $(CFLAGS) $(INC) -c $<
+
+flash: $(PROJECT).hex
+       $(AVRDUDE) -U flash:w:$<:i
\ No newline at end of file
diff --git a/timer-demo.c b/timer-demo.c
new file mode 100644 (file)
index 0000000..83fe5e6
--- /dev/null
@@ -0,0 +1,136 @@
+/* Copyright (c) 2015 Pat Thoyts <patthoyts@users.sourceforge.net>
+ *
+ * Demonstrate the use of AVR timer 2 to precisely control a pin.
+ * Test board has LEDs connected to pin 5 and 9 (PD5 and  PB1) on an
+ * Arduino Nano board. Pin 5 is connected to timer2 and demonstrates
+ * control of the timer interval while pin 9 is connected to timer1
+ * and shows the PWM frequency control.
+ */
+#include <avr/pgmspace.h>
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/power.h>
+#include <avr/sleep.h>
+#include <avr/wdt.h>
+#include <util/delay.h>
+
+/*
+ * timer2:
+ * Using a /64 prescaler with 1 16MHz clock and counting from
+ *   6 we get an interval of 1ms exactly between interrupts:
+ *     (64.0 / 16e6) * (256 - 6) = 0.001s
+ *
+ * timer1:
+ * Using Phase/Freq correct mode we can control the PWM freq precisely.
+ *  f = 16e6 / (2 * mul * TOP)
+ *  TOP = 0xf000 yields 125 Hz
+ *  TOP = 64 yields 125 kHz
+ *  TOP = 16 yields 500 kHz
+ *  TOP = 2  yields max of 4 MHz
+ * For IR: 36kHz use 222; 38kHz use 212
+ */
+const uint8_t TIMER_INIT = 6;
+const uint16_t PWM_TOP = 212;
+const uint16_t PWM_MATCH = 212/2; /* square wave, 50% */
+
+volatile uint16_t counter = 0;
+
+ISR(TIMER2_OVF_vect)
+{
+    cli();
+    TCNT2 = TIMER_INIT; /* reset the timer */
+    if (++counter >= 250)
+    {
+        PORTD ^= _BV(PD5);
+        OCR1A = (OCR1A == 0) ? PWM_MATCH : 0;
+        counter = 0;
+    }
+    sei();
+}
+
+static void init_timer2()
+{
+    cli();
+    /* 18.9: Disable the timer2 overflow interrupt for configuration */
+    TIMSK2 &= ~(1 << TOIE2);
+    /* 18.9b: Select clock source as internal i/o clock */
+    ASSR &= ~(1<<AS2);
+    /* Table 18.8: all 0 == normal operation with overflow */
+    TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
+    TCCR2B &= ~(1<<WGM22);
+    /* Table 18-9: Configure prescaler to CPU/64 */
+    TCCR2B &= (1<<CS22) | (1<<CS21) | (1<<CS20); /* clear bits */
+    TCCR2B |= (1<< CS22);                        /* select /64 */
+    /* Delay is (prescale / cpu_freq) * (256 - tcnt2) */
+    TCNT2 = TIMER_INIT;
+    /* 18.9e: re-enable the timer overflow interrupt flag */
+    TIMSK2 |= (1 << TOIE2);
+    sei();
+}
+
+static void init_pwm_pin9()
+{
+    /* 
+     *  PD5,PD6 (5,6) are attached to timer0 (8 bit)
+     *  PB1,PB2 (9,10) are attached to timer1 (16bit)
+     *  PD3,PB3 (3 & 11) are attached to timer2 (8 bit)
+     *
+     * In Phase Correct PWM mode, the couner counts up to TOP
+     * then back down to 0. OCR is cleared on match ascending then
+     * set on match descending. In this mode the PWM interval is twice
+     * the prescaled clock interval.
+     *
+     * PWM cycle should be (1.0 / 16e6) * 256 == 16us
+     * with prescale 1. In Phase Correct mode we get 32us per cycle
+     * or 31.25kHz.
+     * freq = 16e6/(2 * prescaler * 256) == 31.25
+     * but we can change ICR1 to set alternate TOP other than 256.
+     * eg: ICR1 = 100 yields 80kHz
+     */
+    cli();
+    DDRB |= _BV(DDB1); /* set the pin PB1 (OC1A) as output. */
+    PORTB &= ~(_BV(PB1));
+    /* 16.11.1: select PWM mode: CTC mode */
+    TCCR1A &= ~(_BV(COM1A1) | _BV(COM1A0));
+    TCCR1A |= _BV(COM1A1);
+    /* 16.11.1 (table 16-4): select waveform mode: Phase correct 8 bit */
+    TCCR1A &= ~(_BV(WGM11) | _BV(WGM10));
+    TCCR1B &= ~(_BV(WGM13) | _BV(WGM12));
+    TCCR1A |= _BV(WGM11); TCCR1B |= _BV(WGM13);
+    /* 16.11.2: select clock prescaler: /1 */
+    TCCR1B &= ~(_BV(CS12) | _BV(CS11) | _BV(CS10));
+    TCCR1B |= _BV(CS10);
+    /* set duty cycle: 0 is off, 255 is full on */
+    ICR1 = PWM_TOP;
+    TCNT1 = 0;
+    OCR1A = PWM_MATCH;
+    sei();
+}
+
+int
+main(void)
+{
+    wdt_enable(WDTO_1S);
+
+    /* turn off all unused peripherals */
+    power_adc_disable();
+    power_spi_disable();
+    power_twi_disable();
+    power_usart0_disable();
+
+    /* set all unused pins to high-Z state */
+    DDRB = 0; PORTB = 0xff;
+    DDRC = 0; PORTC = 0xff;
+    DDRD = 0; PORTD = 0xff;
+
+    DDRD = _BV(DDD5); /* pins 5 is output */
+    init_timer2();
+    init_pwm_pin9();
+    
+    while (1)
+    {
+        _delay_ms(250);
+        wdt_reset();
+    }
+}
\ No newline at end of file