Reputation: 844
I want to generate a PWM wave in the PWM mode of timer0 in ATMEGA8 like the figure below:
It has a 20% duty cycle but it can't be implemented with PWM mode alone. I have tried using the fast PWM mode in reversed mode and tried to check for the TCNT0 until it gets to 64H so I can clear the OC0 PIN when it reaches it.
I was wondering if this method works correctly when I am clearing OC0 manually?
And Here is my code:
.DEF A = R16 ;GENERAL PURPOSE ACCUMULATOR
.ORG $0000
ON_RESET:
SBI DDRB,3 ;SET PORTB3(OC0) FOR OUTPUT
LDI A,0b01011011 ;SET TO FAST PWM MODE
OUT TCCR0,A ;SET PRESCALER/DIVIDER TO /32
LDI A,32 ;DIFFERENT VALUE
OUT OCR0,A ;FOR COMPARE
MAIN_LOOP:
PLOOP: IN A,TCNT0 ;COMPARE TCNT0
ANDI A,0x64H ;COMPARE TCNT0 TO 64 TO MAKE IT ZERO
BRNEQ PLOOP
CBI PINB,3
RJMP MAIN_LOOP;A CHECK FOR TIMER LOOP
Upvotes: 2
Views: 4619
Reputation: 15887
It is possible, but requires CPU assistance, and not PWM mode. If you enable the output comparator match interrupt, and the CPU can react within the 50 cycles your pulse lasts, you can leave the output compare mode to toggle and just alternate between the two match values. This is easily done with a single xor operation.
// Setup
TCCR0A = 1<<COM0A0; // toggle output, immediate update (not PWM mode)
OCR0A = 0x32; // initial toggle time
TIMSK0 |= 1<<OCIE0A; // enable interrupt on output compare
TCCR0B = 1<<CS01; // start counter
// In interrupt handler for TIMER0_COMPA
OCR0A ^= 0x32 ^ 0x64; // toggle compare value
Tricks like changing the output value directly from CPU, or resetting the timer value, exposes the precise timing of the CPU instructions on external signals. If you're good at cycle counting this is doable but a lot of effort (compare, for instance, lft's craft demo). The point of PWM mode in the AVR is twofold; firstly it adds the second edge on timer overflow, and secondly it delays updates to the next timer period. The combination lets you alter the pulse width at any time without causing glitches, but it is not intended to do phase offsets like in your example.
If you don't want to use interrupts yet, you could equally check the interrupt flag in your polling loop (just don't set OCIE0A):
if (TIFR0 & (1<<OCF0A)) {
TIFR0 = 1<<OCF0A; // clear interrupt flag
OCR0A ^= 0x32 ^ 0x64; // toggle compare value
}
By the way, your edge values don't give 20%; 256 simply isn't evenly divisible by 5. At 19.5% it may be close enough (though the next two ranges are 19.9% and 20.3%), but if you want exactly 20% it's time to look at CTC mode (which uses the 0A comparator for programming the top value, so you're left with the 0B comparator for PWM output).
Upvotes: 0
Reputation: 8941
PWM cycles in the AVR usually start with a timer overflow (except phase correct and phase/frequency correct PWM) ... in other words you have control on the duty cycle but not on the start. In your example - for whatever reasons - you want to control the start of the PWM cycle (at 32h from timer overflow) as well as the duty cycle (ca. 20% ... 32h of FFh).
So you may want to consider
I quickly hijacked one of my PWM LED things on my AT90USBKEY2 - using an AT90USB1287 processor - and modified it acc. to the above (sorry the code is a bit lengthy :-o ... see below)
Edit:
Strictly speaking all this only makes sense if you have a point of synchronisation ... if you look at the 20% waveform in isolation you cannot determine if it starts at timeslot 0x00
or 0x32
... an oscilloscope would always sync at the rising (or falling) edge of the pulse. So you would need to provide a reference to the start of the PWM frame by output of a pulse on a different pin at the overflow of PWM_SUBLEVEL
. Using this pulse from the other pin as a sync source for an oscilloscope you start to see the move of the phase as you start changing PWM_ON
.
/*
* AsmFile1.asm
*
* Created: 19.05.2015 22:01:49
* Author: MikeD
*/
.nolist
.include <usb1287def.inc>
.list
.def TMP1 = R16
.def TMP2 = R17
.def PWM_SUBLEVEL = R18
.def PWM_ON = R19
.def PWM_OFF = R20
.cseg
.org 0x0000
jmp V_RESET ; 1 $0000 RESET External pin, Power-on reset, Brown-out reset, Watchdog reset, and JTAG AVR reset
jmp V_NOINT ; 2 $0002 INT0 External Interrupt Request 0
jmp V_NOINT ; 3 $0004 INT1 External Interrupt Request 1
jmp V_NOINT ; 4 $0006 INT2 External Interrupt Request 2
jmp V_NOINT ; 5 $0008 INT3 External Interrupt Request 3
jmp V_NOINT ; 6 $000A INT4 External Interrupt Request 4
jmp V_NOINT ; 7 $000C INT5 External Interrupt Request 5
jmp V_NOINT ; 8 $000E INT6 External Interrupt Request 6
jmp V_NOINT ; 9 $0010 INT7 External Interrupt Request 7
jmp V_NOINT ; 10 $0012 PCINT0 Pin Change Interrupt Request 0
jmp V_NOINT ; 11 $0014 USB General USB General Interrupt request
jmp V_NOINT ; 12 $0016 USB Endpoint/Pipe USB ENdpoint/Pipe Interrupt request
jmp V_NOINT ; 13 $0018 WDT Watchdog Time-out Interrupt
jmp V_NOINT ; 14 $001A TIMER2 COMPA Timer/Counter2 Compare Match A
jmp V_NOINT ; 15 $001C TIMER2 COMPB Timer/Counter2 Compare Match B
jmp V_T2OVF ; 16 $001E TIMER2 OVF Timer/Counter2 Overflow
jmp V_NOINT ; 17 $0020 TIMER1 CAPT Timer/Counter1 Capture Event
jmp V_NOINT ; 18 $0022 TIMER1 COMPA Timer/Counter1 Compare Match A
jmp V_NOINT ; 19 $0024 TIMER1 COMPB Timer/Counter1 Compare Match B
jmp V_NOINT ; 20 $0026 TIMER1 COMPC Timer/Counter1 Compare Match C
jmp V_NOINT ; 21 $0028 TIMER1 OVF Timer/Counter1 Overflow
jmp V_NOINT ; 22 $002A TIMER0 COMPA Timer/Counter0 Compare Match A
jmp V_NOINT ; 23 $002C TIMER0 COMPB Timer/Counter0 Compare match B
jmp V_NOINT ; 24 $002E TIMER0 OVF Timer/Counter0 Overflow
jmp V_NOINT ; 25 $0030 SPI, STC SPI Serial Transfer Complete
jmp V_NOINT ; 26 $0032 USART1 RX USART1 Rx Complete
jmp V_NOINT ; 27 $0034 USART1 UDRE USART1 Data Register Empty
jmp V_NOINT ; 28 $0036 USART1TX USART1 Tx Complete
jmp V_NOINT ; 29 $0038 ANALOG COMP Analog Comparator
jmp V_NOINT ; 30 $003A ADC ADC Conversion Complete
jmp V_NOINT ; 31 $003C EE READY EEPROM Ready
jmp V_NOINT ; 32 $003E TIMER3 CAPT Timer/Counter3 Capture Event
jmp V_NOINT ; 33 $0040 TIMER3 COMPA Timer/Counter3 Compare Match A
jmp V_NOINT ; 34 $0042 TIMER3 COMPB Timer/Counter3 Compare Match B
jmp V_NOINT ; 35 $0044 TIMER3 COMPC Timer/Counter3 Compare Match C
jmp V_NOINT ; 36 $0046 TIMER3 OVF Timer/Counter3 Overflow
jmp V_NOINT ; 37 $0048 TWI 2-wire Serial Interface
jmp V_NOINT ; 38 $004A SPM READY Store Program Memory Ready
V_RESET:
; prepare stack ... special write procedure
ldi TMP1, low(ramend)
ldi TMP2, high(ramend)
out spl, TMP1
out sph, TMP2
; increase CLKIO from 1 to 8MHz ... special write procedure
ldi TMP1, 0b1000_0000
ldi TMP2, 0b0000_0000
sts CLKPR, TMP1
sts CLKPR, TMP2
; initialize variables
clr PWM_SUBLEVEL
ldi PWM_ON, 0x32
ldi PWM_OFF, 0x64
; prepare LED ports
; D2-RD on PORTD4
; D2-GN on PORTD5
; D5-RD on PORTD7
; D5-GN on PORTD6
ldi TMP1, 0b1111_0000
out DDRD, TMP1
; Timer2 (8bit) without prescaler in normal mode
; generates interrupt every 256 CPU clock cycles (32 us) for PWM sublevel
; we use this for PWM as Timer2 has the highest priority amongst timers
clr TMP1
sts TCCR2A, TMP1 ; normal mode, port pins disabled
ldi TMP1, (1 << CS20)
sts TCCR2B, TMP1 ; internal clock, no prescaler
ldi TMP1, (1 << TOIE2)
sts TIMSK2, TMP1 ; overflow interrupt enable
sei ; set general interupt enable flag
MAIN:
rjmp MAIN
V_T2OVF:
; fires every 32 us
inc PWM_SUBLEVEL ; overflows every 8.192 ms, f=122.07... Hz
cp PWM_SUBLEVEL, PWM_ON
breq GO_HI
cp PWM_SUBLEVEL, PWM_OFF
breq GO_LO
reti
GO_HI:
sbi PORTD, PORTD4
reti
GO_LO:
cbi PORTD, PORTD4
reti
V_NOINT:
; fire error LED ... if we get here something is wrong
sbi PIND, PIND7 ; invert output by writing 1 to input bit in output mode
reti
Upvotes: 2