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

Go To Last Post
2 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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

by RetroDan@GMail.com

TABLE OF CONTENTS:

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).

        ;--------------------------------------------------; 
        ; ATNT_BLINKY1.ASM                                 ; 
        ; 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
reload the counter each time.

                       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 

For the remaining chapters I invite you to download the PDF version below:
(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
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

.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