Freelancer
Freelancer

Reputation: 844

How to make PWM pulse with 20% duty cycle in AVR?

I want to generate a PWM wave in the PWM mode of timer0 in ATMEGA8 like the figure below:

enter image description here

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

Answers (2)

Yann Vernier
Yann Vernier

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

MikeD
MikeD

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

  • to use a free running timer generating an interrupt every 1/FFh
  • use a single 8 bit register which you increment during each call to the interrupt (it will overflow after FF ... that's OK)
  • if this register reads 32h or 64h, invert the output pin (or set / reset as below)
  • initialize your program by setting the output pin to 0 before SEI

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

Related Questions