Search code examples
timerfrequencyavr16-bit

How to set frequency and duty cycle of Timer1 in 16bit P&F correct PWM


I am using a 328P (Running on the internal 8mhz) to generate a Square wave at around 111K hz or 120K hz with and adjustable duty cycle.

I'm fairly new to doing this level of hardware manipulation so please excuse my ignorance, I spent 3 days looking online and in the datasheet but I'm afraid my understanding of what the acronyms and abbreviations mean is still too limited.

So far I have come up with the following code after doing the research online and in the datasheet. But I feel like I'm missing something, What register manipulates frequency and what register manipulates duty cycle in (Datasheet 20.12.5) Phase and Frequency Correct PWM Mode?

void setup(){
    DDRB |= (1 << DDB1); //Set PB1 as output
    OCR1A = (Unsure of what TOP should be here);
    TCCR1A = (1 << WGM10) | (1 << COM1B0) | (1 << COM1A0);
    TCCR1B = (1 << CS10) | (1 << WGM13);
}
void loop(){
    //manipulate duty cycle with other code here
}

What am I missing or what should I be doing differently here? I tried to use the online AVR Timer Calc to help me get clock ticks needed for that frequency. It says a total of 72 Timer ticks with no prescaler and 16 bit timer would yield a 111Khz (approximate) Square wave. Jumping to 73 or 71 jumps the frequency too much out of the desired range. is there any way to get that closer on an AVR?


Solution

  • After a lot of research along side a friend of mine who is also an EE and works with embedded systems we came to find the best solution for getting what I needed out of the 328p. I will post the solution below for anyone who is interested and needs something similar.

    void setup() {
      // put your setup code here, to run once:
      //Set Timer1 for around 109Khz signal
      cli();//stop interrupts
      TCCR1A = 0;// set entire TCCR1A register to 0
      TCCR1B = 0;// same for TCCR1B
      TCNT1  = 0;//initialize counter value to 0
      //Set register bits 
      TCCR1A = _BV(COM1A1) | _BV(WGM11);
      TCCR1B = _BV(CS10) | _BV(WGM12) | _BV(WGM13);
      ICR1 = 73; // frequency = 8000000/73 = 109.589 kHz
      OCR1A = 0; //0% Duty Cycle or Full Off.
      sei();//allow interrupts
    }
    
    void loop() {
      // put your main code here, to run repeatedly:
      OCR1A = 36; //50% Duty Cycle 73/2 = 36.5 Can be changed as needed.
    }
    

    This code sets the registers for Timer 1 so it shouldn't interfere with millis or other timing functions in the Arduino libraries. I needed to manipulate a specific pin anyways and PB1 (OC1A) (or Arduino digital pin 9) is the pin that this will oscillate.

    You can change ICR1 to any value you need based on some simple math, Your clock frequency divided by the value of the counter equals the approximate frequency produced. OCR1A sets the duty cycle of the signal.

    You are limited in the exact frequency but for my needs this worked out OK. I was still able to use it to drive the transducer I was using.

    This was a quick answer to the initial problem and allows me to change the duty cycle as a bonus. I don't remember the exact information on the registers we set, When I have time I will update this answer with the info from the data sheet pertaining to that.