0
votes

I am well familiar with PWM generation in Atmega128 and its family microcontrollers. I have been using prescalar and other registers for generating frequency. But I have to generate 20KHz pwm signal. I tried but I could not get the desired output. Can anyone suggest me or help me how to do it ?

As far as I know, in atmega128, 1 instruction takes 1 cycle. Using 16MHz crystal, 1 instruction completes in 1/16M sec. I tried to generate 20Khz signal (50 us)with 25us duty cycle. But I get different frequency (277.78 Hz) in oscilloscope which is far less than 20KHz My calculation was 16MH = 20000Hz * 800. for 0-399 count, I made port high and 399-799 count, I made port low.

void frequency(void){   // 20kHz Frequency  
    if (cnt1 <= 399){
        PORTB |= (1<<7);
    } else {
        PORTB &= ~(1<<7);
    }
    cnt1++;
    if (cnt1 >= 800)    cnt1 = 0;
}
2
When I've approached this problem with an Xmega, I was surprised at how many steps it took to get a solid computed frequency working. (See craigbot.blogspot.it/2014/01/stepper-gnomebot.html for an example.) Most of the time, I just do it empirically with a single loop for high frequencies, and a loop within a loop for lower frequencies, matching the iterator count(s) to the oscilloscope result. It's linear, so it's fairly straightforward to do.Dribbler
Have you looked at your assembly output and checked how many instruction the compiler really threw in for you? Alternatively you may want to consider timer based interrupts to get timing under control.MikeD

2 Answers

2
votes

I don't have access to the 128 but verified its 16-bit Timer 1 is similar to that in the 328 and 32U4 so the following should work with minor modification (the main sticking point is probably looking up what pin the overflow register is bound to):

#include <avr/io.h>
#include <util/delay.h>

struct CTC1
{
    static void setup()
    {
        // CTC mode with TOP-OCR1A

        TCCR1A = 0;
        TCCR1B = _BV(WGM12);

        // toggle channel A on compare match

        TCCR1A = (TCCR1A & ~(_BV(COM1A1) | _BV(COM1A0))) | _BV(COM1A0);

        // set channel A bound pin PB1 to output mode

#if defined(__AVR_ATmega32U4__)
        DDRB |= _BV(5);
#else
        DDRB |= _BV(1);
#endif
    }

    static void set_freq(float f)
    {
        static const float f1 = min_freq(1), f8 = min_freq(8), f64 = min_freq(64), f256 = min_freq(256);

        uint16_t n;

        if (f >= f1)        n = 1;
        else if (f >= f8)   n = 8;
        else if (f >= f64)  n = 64;
        else if (f >= f256) n = 256;
        else                n = 1024;

        prescale(n);

        OCR1A = static_cast<uint16_t>(round(F_CPU / (2 * n * f) - 1));
    }

    static void prescale(uint16_t n)
    {
        uint8_t bits = 0;

        switch (n)
        {
            case    1:  bits = _BV(CS10);               break;
            case    8:  bits = _BV(CS11);               break;
            case   64:  bits = _BV(CS11) | _BV(CS10);   break;
            case  256:  bits = _BV(CS12);               break;
            case 1024:  bits = _BV(CS12) | _BV(CS10);   break;
            default:    bits = 0;
        }

        TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11) | _BV(CS10))) | bits;
    }

    static inline float min_freq(uint16_t n)
    {
        return ceil(F_CPU / (2 * n * 65536));
    }
};

void setup()
{
    CTC1::setup();
    CTC1::set_freq(20e3);
}

void loop()
{
    // do whatever
    _delay_ms(1);
}

int main()
{
    setup();
    for (;;)
        loop();
}

I tested on my scope and measure exactly 20kHz off a 328p running at 16MHz. If 20kHz is the only frequency you need then you can simplify this substantially. Translating the code to use one of the 8-bit timers is also straightforward though I haven't verified that it's possible to hit exactly 20kHz with those.

0
votes

It's not a good idea to use counter in C to implement the PWM or anything time critical really. Although C converts your code to specific machine code, you don't really know how much time it will take. Your code does not translate to:

make port B high 400 times (PORTB |= (1<<7);)
make port B low 400 times (PORTB &= ~(1<<7);)

, but rather something like this (simplification, human-readable):

load variable cnt1 to memA;
load 399 to memB
compare mem A to memB
put result to memC
if memC eq "somthing indicating <=" do PORTB |= (1<<7);
if memC something else do PORTB &= ~(1<<7);
load cnt1 to memD and increment;
write memD to cnt1;
load 800 to memE
load cnt1 to memF
compare memF to memE
put result to memG
if memG eq "somthing indicating <=" do memF = 0, write memF to cnt1;
if memG something else go to start;

If you look at this from "C" point of view you need to do at least:

 1. comare cnt1-399
 2. if ok - do / else
 3. port high / port low
 4. add one to cnt1
 5. compare cnt1 and 800

It then depends on you compiler how good it is at optimizing all the loads and writes (usually quite good). You can have control on what the delays will be if you really know your compiler and don't use to much optimization (it is usually to complex to follow) or by writing the code in assembler. But then you will have to use logic similar to my explanation of the machine code (assembler is close to human-readable machine code).

I think the solution for you are timer interrupts. There's a good tutorial for atmega128 this here.

Also what do you mean with:

I tried to generate 20Khz signal (50 us)with 25us duty cycle.

Do you mean 20kHz signal with 50% duty cycle? so 25us low, 25 us high?

If this is the case you can do this with one timer interrupt and one (binary) counter. Exactly the "8 bit timer example" you can read about in the provided link.