Interrupt driven routine for Avr 16-bit PWM

Posted in June 28th, 2007

Target: ATtiny2313
Uses OCR0A, TCNT0, TCNT1

With ATtiny2313 it is possible to utilize two 16-bit using OC1A and OC1B.
If application needs more than this, say controller, two additional 16-bit channels can be created from native 8-bit OC0A and OC0B. The resolution is true 16-bit, glitch-free. Prescaler can be set to 1.
The software emulated PWM behaves as its hardware counterpart, except that its registers are in data SRAM, here called RegOCR0AL and RegOCR0AH.
A small limitation is that duty cycle can not be too small. The value of RegOCR0AH:RegOCR0AL must be limited to, say FF80h (inverted PWM) depending on particular program environment.

In this code example are shown only essential parts of software PWM implementation. The goal is to supply the basic idea of how to create 16-bit PWM using 8-bit OC and 16-bit timer, in this case OC0A and TCNT1.
Other parts such as initialization and main program are not shown because they are application specific.

;****************************************************************
; ****    CONSTANTS
;****************************************************************
.equ    TCCR0A_Set_OC_Val    = (1<<COM0A1) | (1<<COM0A0)
.equ    TCCR0A_Clear_OC_Val  = (1<<COM0A1) | (0<<COM0A0)

;****************************************************************
; ****    GLOBAL VARIABLES
;****************************************************************
.dseg
RegOCR0AL:            .byte    1
RegOCR0AH:            .byte    1

;****************************************************************
; ****    INTERRUPT VECTORS
;****************************************************************
.cseg
.org    $0000
    rjmp    Reset                ; Reset handler                     $0000
    rjmp    INT0_Int_Entry       ; External Interrupt0               $0001
    rjmp    INT1_Int_Entry       ; External Interrupt1               $0002
    rjmp    ICP1_Int_Entry       ; Input capture interrupt 1         $0003
    rjmp    OC1A_Int_Entry       ; Timer/Counter1 Compare Match A    $0004
    rjmp    OVF1_Int_Entry       ; Overflow1 Interrupt               $0005
    rjmp    OVF0_Int_Entry       ; Overflow0 Interrupt               $0006
    rjmp    URXC0_Int_Entry      ; USART0 RX Complete Interrupt      $0007
    rjmp    UDRE0_Int_Entry      ; USART0 Data Register Empty        $0008
    rjmp    UTXC0_Int_Entry      ; USART0 TX Complete Interrupt      $0009
    rjmp    ACI_Int_Entry        ; Analog Comparator Interrupt       $000A
    rjmp    PCINT_Int_Entry      ; Pin Change Interrupt              $000B
    rjmp    OC1B_Int_Entry       ; Timer/Counter1 Compare Match B    $000C
    rjmp    OC0A_Int_Entry       ; Timer/Counter0 Compare Match A    $000D
    rjmp    OC0B_Int_Entry       ; Timer/Counter0 Compare Match B    $000E
    rjmp    USI_START_Int_Entry  ; USI start interrupt               $000F
    rjmp    USI_OVF_Int_Entry    ; USI overflow interrupt            $0010
    rjmp    ERDY_Int_Entry       ; EEPROM write complete             $0011
    rjmp    WDT_Int_Entry        ; Watchdog Timer Interrupt          $0012

;****************************************************************
; ****    INTERRUPT ENTRY
;****************************************************************
.cseg

OC0A_Int_Entry:
    push    zl
    in      zl, SREG
    push    zl
    push    zh
    push    yl
    push    yh
;-------------------------------

    lds     yl, RegOCR0AL            ; OCR low byte
    lds     yh, RegOCR0AH            ; OCR hihg byte
    in      zl, TCNT1L               ; timer low byte
    in      zh, TCNT1H               ; timer high byte

    cpi     zh, $FF                  ; top value
    breq    _FF_Clear

    sub     zl, yl
    sbc     zh, yh

    brcc    RegOCR0AL_Set
    cpi     zh, $FE
    breq    Wait_a_little            ; FE, <= 200h MCU cycles
    brcc    RegOCR0AL_Set            ; FF, <= 100h MCU cycles
    rjmp    RegOCR0AL_Clear          ; > 200h MCU cycles

Wait_a_little:
    cpi     zl, $E0
    brcs    RegOCR0AL_Clear          ; too many MCU cycles, don't wait

    in      zl, TCNT0
    cp      zl, yl                   ; RegTCNT0L, RegOCR0AL
    brcs    PC - 2

    rjmp    RegOCR0AL_Set            ; < 100h MCU cycles now

_FF_Clear:
    out     OCR0A, zh
    ldi     zl, TCCR0A_Clear_OC_Val
    out     TCCR0A, zl               ; clear output on compare
    rjmp    Leave_OC0A_Int

RegOCR0AL_Set:
    out     OCR0A, yl
    ldi     zl, TCCR0A_Set_OC_Val
    out     TCCR0A, zl               ; set output on compare
    rjmp    Leave_OC0A_Int

RegOCR0AL_Clear:
    out     OCR0A, yl
    ldi     zl, TCCR0A_Clear_OC_Val
    out     TCCR0A, zl               ; clear output on compare
    rjmp    Leave_OC0A_Int

;------------------------------
Leave_OC0A_Int:
    pop     yh
    pop     yl
    pop     zh
    pop     zl
    out     SREG, zl
    pop     zl
    reti

Follow-up responses to this entry through the RSS feed, Leave a Reply or Trackback from your own site.

Leave a Reply

 Username (*required)

 Email Address (*private)

 Website (*optional)