Pages: [1]   Go Down
Author Topic: Is there a way to control both frequency and duty cycle for PWM outputs?  (Read 1145 times)
0 Members and 1 Guest are viewing this topic.
Seattle, WA
Offline Offline
Newbie
*
Karma: 0
Posts: 36
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi all, I'm wondering if there is a way to specify both the frequency (e.g. tone()) and duty-cycle (i.e. analogWrite()) for a PWM output? I'm looking into some quasi "frequency modulation" applications that requires that I control both the frequency and duty-cycle. Ideally, I'd like to use the functions in the standard library, if that's even possible.

Thanks in advance!
- K.
Logged

Offline Offline
Jr. Member
**
Karma: 0
Posts: 77
You played your cards but you couldn't win....ED-I-SON'S MEDICINE
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Here's a post to check out:

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1235060559/12

also

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1234764073

This method works as I've messed around with it.  But you can only change the PWM frequency to a few certain values.  I don't know if it'd be possible to do so at any frequency of your choosing.  If it is, I'd like to hear about it!
Logged

Seattle, WA
Offline Offline
Newbie
*
Karma: 0
Posts: 36
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi GlitchBoy, thanks for the links, reading further, I'm going to look into this ... do you know how I can read and write to the TCCRxB timer registers?
Logged

Seattle, WA
Offline Offline
Newbie
*
Karma: 0
Posts: 36
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

One more question: would it be fair to assume as long as I can generate the desired freuency from, say, Timer 2, I can then get PWM output based on that frequency?
Logged

Manchester (England England)
Online Online
Brattain Member
*****
Karma: 618
Posts: 33957
Solder is electric glue
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I can then get PWM output based on that frequency?
Yes.
Quote
how I can read and write to the TCCRxB timer registers
Just treat them like variable names.
TCCR1B = 0x23; // for example
Logged

Offline Offline
Jr. Member
**
Karma: 0
Posts: 77
You played your cards but you couldn't win....ED-I-SON'S MEDICINE
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

Germany
Offline Offline
Edison Member
*
Karma: 136
Posts: 1479
If you believe something is right, you won't see what's wrong (David Straker).
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
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:
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:
// 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
Logged

Seattle, WA
Offline Offline
Newbie
*
Karma: 0
Posts: 36
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Pages: [1]   Go Up
Jump to: