Go Down

Topic: Is there a way to control both frequency and duty cycle for PWM outputs? (Read 1 time) previous topic - next topic

GlitchBoy

Hey primate, check this out:

http://www.arduino.cc/playground/Code/Timer1

I hadn't seen this one before but it's intriguing--sounds like just what you're after.  If you use it and it works, please post here

olikraus

Hi
Recently I wrote a code which programs the timer, based on frequency and duty.
The code is part of my frequency generator, which is an example of my GUI interface library.
Code is hosted here:
http://code.google.com/p/m2tklib/source/browse/arduino/glcd/FreqGen/FreqGen.pde
Project picture is here:
http://code.google.com/p/m2tklib/wiki/FreqGen

In the code below, the parameters are assigned to global variables:

Code: [Select]

uint8_t fc_pin = 3; // 3, 5, 11 (only OCRnB)
uint32_t fc_user_freq; // requested user frequency
uint8_t fc_duty = 1; // PWM value 1..99 %


Then simply call
Code: [Select]

void fc_calc(void)

which programs the timer based on the values above.
Any frequency can be assigned to fc_user_freq, however not all frequencies are possible, so fc_calc() will choose a frequency next to the requested value. E.g. for 5800Hz it will choose 5813Hz. The value 5813Hz can be found in the variable fc_calc_freq.

Code: [Select]

// Timer 0 & 1,        N = { 1, 8, 64, 256, 1024 }
uint8_t fc_prescalar_div01[] = { 3, 3, 2, 2, 0 };
// Timer 2, N = { 1, 8, 32, 64, 128, 256, 1024 }
uint8_t fc_prescalar_div2[] = { 3, 2, 1, 1, 1, 2, 0 };

// input values
uint8_t fc_pin = 3; // 3, 5, 11 (only OCRnB)
uint32_t fc_io_freq = F_CPU;
uint32_t fc_user_freq; // requested user frequency
uint8_t fc_duty = 1; // PWM value 1..99 %
#define FC_DUTY_MAX 100

// output values
uint8_t fc_timer = 0; // timer: 0, 1, 2
uint8_t fc_ab = 0; // 0 = A, 1 = B
uint8_t fc_prescalar_idx; // calculated value for CS22:0 bits
uint8_t fc_top_value; // calculated value for OCRnA
uint32_t fc_calc_freq; // calculated real frequency
uint8_t fc_calc_duty; // calculated real duty


// based on the fc_pin value, calculate the timer
void fc_calc_timer_ab(void)
{
  switch(fc_pin)
  {
    case 5: fc_timer = 0; fc_ab = 1; break;
    //case 6: fc_timer = 0; fc_ab = 0; break;
    //case 9: fc_timer = 1; fc_ab = 0; break;
    case 10: fc_timer = 1; fc_ab = 1; break;
    // case 11: fc_timer = 2; fc_ab = 0; break;
    case 3: fc_timer = 2; fc_ab = 1; break;
    default: fc_timer = 0; fc_ab = 1; break;
  }
}

// Fast PWM Mode:      f = fc_io_freq / ( N * ( 1 + OCRnA) )
//  ==>    N * ( 1 + OCRnA) = fc_io_freq / f = q
// Algorithm:
//    calculate q
//    try values for N, until q/N is below 256
//    if found, then OCRnA = q/N - 1
uint8_t fc_calc_prescalar_idx_and_top_value(void)
{
  uint8_t idx = 0;
  uint8_t div;
  uint32_t q;
  q = fc_io_freq;
  q /= fc_user_freq;
  for(;;)
  {
    if ( q <= 256UL )
    {
      idx++;
      fc_prescalar_idx = idx;
      q--;
      fc_top_value = q;
      return  1;
    }
    if ( fc_timer == 2 )
      div = fc_prescalar_div2[idx];
    else
      div = fc_prescalar_div01[idx];
    if ( div == 0 )
      break;
    q >>= div;
    idx++;
  }
  fc_prescalar_idx = 0;
  fc_top_value = 0;
  return 0; // no prescalar found
}

// calculate frequency from prescalar index and top value
// f = fc_io_freq / ( N * ( 1 + OCR2A) )
void fc_calc_frequency_and_duty(void)
{
  uint32_t t;
  uint8_t idx = fc_prescalar_idx;
  if ( idx == 0 )
  {
    fc_calc_freq = 0;
    return ;
  }
  t = fc_top_value;
  t++;
 
  idx--;
  while(  idx > 0 )
  {
    idx--;
    if ( fc_timer == 2 )
      t <<= fc_prescalar_div2[idx];
    else
      t <<= fc_prescalar_div01[idx];
  }
  fc_calc_freq =  fc_io_freq;
  fc_calc_freq /= t;

  fc_calc_duty = ((uint16_t)fc_top_value * (uint16_t)fc_duty)/FC_DUTY_MAX;
 
}

// OCRnA contains the top value
// OCRnB contains the duty value
// --> Only port pin B can be used
void fc_set_hw(void)
{
  uint8_t d;

  d = fc_calc_duty;
   
  pinMode(fc_pin, OUTPUT);
  //analogWrite(fc_pin, 40);
  //digitalWrite(fc_pin, HIGH);

  if ( fc_timer == 0 )
  {
    // Set fast PWM mode, TOP is OCRA
    TCCR0A |= _BV(WGM01) | _BV(WGM00);
    TCCR0B |= _BV(WGM02);

    // none inverting fast PWM
    if ( fc_ab == 0 )
    {
      TCCR0A |= _BV(COM0A1);
      TCCR0A &= ~_BV(COM0A0);
    }
    else
    {
      TCCR0A |= _BV(COM0B1);
      TCCR0A &= ~_BV(COM0B0);
    }
   
    // prescalar
    TCCR0B &= ~7;
    TCCR0B |= fc_prescalar_idx;
   
    // set top value
    OCR0A = fc_top_value;
    // set duty
    OCR0B = d;
   
   
  }
  else if ( fc_timer == 1 )
  {
    // Set fast PWM mode, TOP is OCRA
    TCCR1A |= _BV(WGM11) | _BV(WGM10);
    TCCR1B |= _BV(WGM12) | _BV(WGM13) ;
   
    // none inverting fast PWM
    if ( fc_ab == 0 )
    {
      TCCR1A |= _BV(COM1A1);
      TCCR1A &= ~_BV(COM1A0);
    }
    else
    {
      TCCR1A |= _BV(COM1B1);
      TCCR1A &= ~_BV(COM1B0);
    }
   
    // prescalar
    TCCR1B &= ~7;
    TCCR1B |= fc_prescalar_idx;
   
    // set top value
    sei();
    OCR1AH = 0;
    OCR1AL = fc_top_value;
    // set duty
    OCR1BH = 0;
    OCR1BL = d;
    cli();
   
  }
  else if ( fc_timer == 2 )
  {
   
    // ensure internal clock
    ASSR &= ~(_BV(EXCLK) | _BV(AS2));
   
    // Set fast PWM mode, TOP is OCRA
    TCCR2A |= _BV(WGM21) | _BV(WGM20);
    TCCR2B |= _BV(WGM22);

    // none inverting fast PWM
    if ( fc_ab == 0 )
    {
      TCCR2A |= _BV(COM2A1);
      TCCR2A &= ~_BV(COM2A0);
    }
    else
    {
      TCCR2A |= _BV(COM2B1);
      TCCR2A &= ~_BV(COM2B0);
    }
   
    // prescalar
    TCCR2B &= ~7;
    TCCR2B |= fc_prescalar_idx;
   
    // set top value
    OCR2A = fc_top_value;
    // set duty
    OCR2B = d;
  }
}

// input: fc_timer, fc_user_freq
void fc_calc(void)
{
  fc_calc_timer_ab();
  fc_calc_prescalar_idx_and_top_value();
  fc_calc_frequency_and_duty();
  fc_set_hw();
}


Oliver

primate

Thanks for all the replies! I'll look into all these possible solutions and let you know if I figure it out!

Go Up