microcontrolleravratmegapwm

Generate an arbitrary PWM signal using ATmega128


I am well familiar with PWM generation in ATmega128 and its family microcontrollers. I have been using a prescaler and other registers for generating frequency. But I have to generate 20 kHz PWM signal. I tried, but I could not get the desired output. How can I do it?

As far as I know, in ATmega128, one instruction takes one cycle. Using a 16 MHz crystal, one instruction completes in 1/16M seconds.

I tried to generate 20 kHz signal (50 µs) with a 25 µs duty cycle. But I get a different frequency (277.78 Hz) on the oscilloscope, which is far less than 20 kHz.

My calculation was

16 MHz = 20000 Hz * 800.

for a 0-399 count. I made the port high. And a 399-799 count, and I made port low.

void frequency(void) {    // 20 kHz frequency
    if (cnt1 <= 399) {
        PORTB |= (1<<7);
    } else {
        PORTB &= ~(1<<7);
    }
    cnt1++;
    if (cnt1 >= 800)
        cnt1 = 0;
}

Solution

  • I don't have access to the ATmega128, but I verified its 16-bit Timer 1 is similar to that in the ATmega328 and ATmega32U4, 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 20 kHz off a ATmega328P running at 16 MHz. If 20 kHz 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 20 kHz with those.