Search code examples
microcontrolleravratmegapwm

generate arbitrary PWM signal using ATMEGA128


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;
}

Solution

  • 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.