Interrupt driven routine for Avr 16-bit PWM
Target: ATtiny2313
Uses OCR0A, TCNT0, TCNT1
With ATtiny2313 it is possible to utilize two 16-bit PWM using OC1A and OC1B.
If application needs more than this, say RGB 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 assembler 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