[TUT][ASM][CODE][PWM] A MORON'S GUIDE TO TIMERS & PWM

5 posts / 0 new
Author
Message

A MORON'S GUIDE TO TIMER/COUNTERS v1.6
=======================================

1. THE PAUSE ROUTINE
2. WAIT-FOR-TIMER "NORMAL" MODE
3. WAIT-FOR-TIMER "NORMAL" MODE (Modified)
4. THE TIMER-COMPARE METHOD
5. THE TIMER OVER-FLOW INTERUPT METHOD
6. THE TIMER OVER-FLOW INTERUPT METHOD (Modified)
7. THE TIMER COMPARE-MATCH INTERUPT METHOD
8. THE CTC "AUTOMATIC" WAVE-FORM MODE
9. THE FAST PWM MODE 7
10. THE PWM FAST MODE 3 WITH DUTY CYCLE
11. THE PWM PHASE CORRECT MODE
12. TIMER MATHEMATICS
13. FINAL PWM PROJECT: DIM AN LED

Computer timer/counters are used to perform tasks at a set interval. A timer that increments a counter
once every second could be used to make a real-time clock by keeping track of the hours, minutes &
seconds that have elapsed.

For this primer I use the AVR ATtiny13 for its simplicity & small number of pins. This chip has one
timer and one I/O port (Port B) of which Bits 0-4 can be used for output. The ATtiny13 runs at 1.2MHz
( 9.6MHz Oscillator/8 ). The example programs should run on the Attiny25, Attiny45 or Attiny85. The
circuits & code should be easy to adapt to most any AVR Core chips.

To show our progress we connect an LED on Port B, Pin 0 to ground and making it blink. If you use
more than 3 volts, insert a 100-220 Ohm resistor in-line with the LED.

The circuit diagram for our ATtiny13 is:

```                   +3VDC      PINOUTS: 1-PORTB,5 (/RESET)
ATTINY13  |                   2-PORTB,3
.-----.  |                   3-PORTB,4
--|1 A 8|--'                   4-GROUND
--|2 T 7|--                    5 PORTB,0
--|3 N 6|--                    6-PORTB,1
.--|4 Y 5|--D LED (PORTB0)      7-PORTB,2
|  `-----'  |                   8-Vcc
|           |
|           |
`-----------+ GND
```

CHAPTER 1: A PAUSE ROUTINE
===========================

A simple timer is a software pause (or wait) routine. They go around a software loop and do nothing
but waste time. By changing the number of times the program goes around the loop we can adjust the
time it spends there.

```                PAUSE         PAUSE       PAUSE
ON:         +------+      +------+      +----->
|      |      |      |      |     ETC.
|      |      |      |      |
OFF: -+-----+      +------+      +------+
PAUSE         PAUSE         PAUSE

```

Here is a program that blinks the LED about twice per second. (An explanation follows the listing).

```        ;--------------------------------------------------;
; AUTHOR: DANIEL J. DOREY (RETRODAN@GMAIL.COM)     ;
;--------------------------------------------------;

.INCLUDE "TN13DEF.INC"   ;(ATTINY13 DEFINITIONS)

.DEF A = R16             ;GENERAL PURPOSE ACCUMULATOR
.DEF I = R20             ;INDEX
.DEF N = R22             ;COUNTER

.ORG 0000
ON_RESET:
SBI DDRB,0           ;SET PORTB0 FOR OUTPUT

;--------------;
; MAIN ROUTINE ;
;--------------;
MAIN_LOOP:
SBI   PINB,0       ;TOGGLE THE 0 BIT
RCALL PAUSE        ;WAIT/PAUSE
RJMP MAIN_LOOP    ;GO BACK AND DO IT AGAIN

;----------------;
;PAUSE ROUTINES  ;
;----------------;
PAUSE: LDI N,0           ;DO NOTHING LOOP
PLUPE: RCALL MPAUSE      ;CALLS ANOTHER DO NOTHING LOOP
DEC N             ;CHECK IF WE COME BACK TO ZERO
BRNE PLUPE       ;IF NOT LOOP AGAIN
RET             ;RETURN FROM CALL

MPAUSE:LDI I,0           ;START AT ZERO
MPLUP: DEC I             ;SUBTRACT ONE
BRNE MPLUP       ;KEEP LOOPING UNTIL WE HIT ZERO
RET             ;RETURN FROM CALL
```

First the assembler reads the file TN13DEF.INC that contains standard definitions for the Attiny13,
then we add some of our own definitions for registers that we use.

```        .INCLUDE "TN13DEF.INC"   ;(ATTINY13 DEFINITIONS)
.DEF A = R16             ;GENERAL PURPOSE ACCUMULATOR
.DEF I = R20             ;INDEX
.DEF N = R22             ;COUNTER
```

.ORG tells the assembler we want our program at bottom of memory (since we have no interrupts):

```        .ORG 0000
```

We use bit zero of Port B as output, so we write a one to the first bit of the Data Direction Register for
Port B (DDRB):

```     ON_RESET:
SBI DDRB,0           ;SET PORTB0 FOR OUTPUT
```

In the main loop we flip the 0-bit of Port B then call the pause routine in an infinite loop:

```        MAIN_LOOP:
SBI   PINB,0       ;FLIP THE 0 BIT
RCALL PAUSE        ;WAIT/PAUSE
RJMP MAIN_LOOP    ;GO BACK AND DO IT AGAIN
```

The next two subroutines waste time by going in loops. The first PAUSE routine calls the second
routine (MPAUSE) to slow things down so we can see the LED blinking. Both routines load zero into a
register then repeatedly subtracts one until it hits zero again (subtracting one from zero gives 255):

```        PAUSE: LDI N,0           ;DO NOTHING LOOP
PLUPE: RCALL MPAUSE      ;CALLS ANOTHER DO NOTHING LOOP
DEC N             ;CHECK IF WE COME BACK TO ZERO
BRNE PLUPE       ;IF NOT LOOP AGAIN
RET             ;RETURN FROM CALL

MPAUSE:LDI I,0           ;START AT ZERO
MPLUP: DEC I             ;SUBTRACT ONE
BRNE MPLUP       ;KEEP LOOPING UNTIL WE HIT ZERO
RET             ;RETURN FROM CALL
```

CHAPTER 2: WAIT-FOR-TIMER "NORMAL" MODE
===========================================

Next we use the chip's timer. When operated in "normal" mode, it will count up from zero to 255 then
set an over-flow flag. So we wait for this flag to be set each time. I think a lot of the confusion about
timers could be eliminated if everyone just called them counters instead. They are counting circuits that
people use to create timers.

```
FLAG             FLAG
0      255      0       255
ON:        +-------+       +-------+
|       |       |       |
|       |       |       |
OFF: ------+       +-------+       +----> ETC.
0     255     0       255     0    255
FLAG            FLAG         FLAG
```

We set a "pre-scaler" to 1024 that divides-down the system clock by 1024 so we can see the LED
blinking. I think a lot of confusion about pre-scalers could be avoided if they were just called dividers
instead. Note, setting the pre-scaler also activates the timer/counter.

```            LDI A,0b0000_0101    ;SET TIMER PRESCALER TO /1024
OUT TCCR0B,A
```

The rest of the code is the same as our last program, except for the PAUSE routine. This time we wait
for the timer to overflow past 255 (and the overflow flag gets set). Then we reset the flag. To reset the
flag we write a one (not a zero):

```         .INCLUDE "TN13DEF.INC"   ;(ATTINY13 DEFINITIONS)
.DEF A = R16             ;GENERAL PURPOSE ACCUMULATOR

.ORG 0000
ON_RESET:
SBI DDRB,0           ;SET PORTB0 FOR OUTPUT
LDI A,0b0000_0101    ;SET TIMER PRESCALER TO /1024
OUT TCCR0B,A

MAIN_LOOP:
SBI   PINB,0       ;FLIP THE 0 BIT
RCALL PAUSE        ;WAIT
RJMP MAIN_LOOP    ;GO BACK AND DO IT AGAIN

PAUSE:
PLUPE: IN   A,TIFR0        ;WAIT FOR TIMER
ANDI A,0b0000_0010  ;(1<<TOV0)
BREQ PLUPE
LDI  A,0b0000_0010  ;RESET FLAG
OUT  TIFR0,A        ;NOTE: WRITE A 1 (NOT ZERO)
RET
```

CHAPTER 3: MODIFIED WAIT-FOR-TIMER "NORMAL" MODE

The last method provides us with only five speeds, because the pre-scalers can only be set to five
values (1,8,64,256,1024). For more control we can pre-load the counter to any value from zero to 255,
this will shorten the time it takes to reach 255 and over-flow. In the example below we pre-load timer
with 128 each time.

```
FLAG        FLAG
128   255   128   255
ON:       +-----+     +-----+
|     |     |     |
|     |     |     |
OFF: -----+     +-----+     +---> ETC.
128   255   128   255   128   255
FLAG        FLAG        FLAG
```

For example, to blink the LED twice as fast we pre-load the timer to 128 with the following two lines:

```              LDI   A,128        ;PRELOAD TIMER WITH 128
OUT   TCNT0,A
```

Our modified program goes twice as fast and our MAIN routine now looks like this:

```         ;--------------;
; MAIN ROUTINE ;
;--------------;
MAIN_LOOP:
SBI   PINB,0       ;FLIP THE 0 BIT
RCALL PAUSE        ;WAIT
LDI   A,128        ;PRELOAD TIMER WITH 128
OUT   TCNT0,A
RJMP MAIN_LOOP    ;GO BACK AND DO IT AGAIN
```

CHAPTER 4: THE TIMER-COMPARE METHOD
(CTC MODE CLEAR-TIMER-on-COMPARE)
=======================================

The next method is very similar to the last. This time we set a "compare" value and a flag goes off
when the timer reaches this value instead of 255. This simplified our program because we don't have to

```                       MATCH       MATCH
0     128   0     128
ON:       +-----+     +-----+
|     |     |     |
|     |     |     |
OFF: -----+     +-----+     +---> ETC.
0    128   0     128   0     128
MATCH       MATCH       MATCH
```

First we configure the timer into "CTC" mode and set the pre-scaler to 1024:

```            LDI A,0b0100_0010    ;SET TO CTC MODE
OUT TCCR0A,A
LDI A,0b0000_0101    ;SET PRESCALER TO /1024 AND
OUT TCCR0B,A
```

We need to set the compare register to the value we want. Once again we use a value of 128:

```            LDI A,128            ;OUR COMPARE VALUE
OUT OCR0A,A          ;INTO THE COMPARE REGISTER
```

Then we wait for the compare flag to be set. (Note that we are waiting for a different flag this time):

```            PAUSE:
PLUPE: IN   A,TIFR0        ;WAIT FOR TIMER
ANDI A,0B0000_0100  ;(1<<OCF0A)
BREQ PLUPE
```

Finally we reset the compare flag after it is triggered, (to be ready for the next compare):

```          LDI  A,0b0000_0100  ;CLEAR FLAG
OUT  TIFR0,A        ;NOTE: WRITE A 1 (NOT 0)
RET
```

The resulting code should now look like this:

```        .INCLUDE "TN13DEF.INC"   ;(ATTINY13 DEFINITIONS)
.DEF A = R16             ;GENERAL PURPOSE ACCUMULATOR

.ORG 0000
ON_RESET:
SBI DDRB,0           ;SET PORTB0 FOR OUTPUT
LDI A,0b0100_0010    ;ACTIVATE CTC MODE
OUT TCCR0A,A         ;SET FLAG ON COMPARE MATCH
LDI A,0b0000_0101    ;
OUT TCCR0B,A         ;SET PRESCALER TO /1024
LDI A,128            ;OUR COMPARE VALUE
OUT OCR0A,A          ;INTO THE COMPARE REGISTER
MAIN_LOOP:
SBI   PINB,0       ;FLIP THE 0 BIT
RCALL PAUSE        ;WAIT
RJMP MAIN_LOOP    ;GO BACK AND DO IT AGAIN

PAUSE:
PLUPE: IN   A,TIFR0        ;WAIT FOR TIMER
ANDI A,0B0000_0100  ;(1<<OCF0A)
BREQ PLUPE
LDI  A,0b0000_0100  ;CLEAR FLAG
OUT  TIFR0,A        ;NOTE: WRITE A 1 (NOT 0)
RET
```

(You must Login to see the Files)

Attachment(s):

Visit AVR Assembler site http://avr-asm.tripod.com

Last Edited: Sun. Sep 19, 2010 - 03:01 AM

.org 0 I was wondering about that. I knew that interrupt vectors where around down there. I tend to just let the assembler gavrasm deal with the org issue. Another great tutorial. Even though I use a atmega8 the code works fine with it, just needs a few weaks here and there.

Cheer
Rich

very nice tutorial i had trouble with timers but it really helped

eager to learn