Go Down

Topic: Multiple PWM Outputs with Unique Frequencies (Read 9532 times) previous topic - next topic

randomvibe

My project involves a motor driver requiring a PWM signal at 1khz and an infrared emitter running at 38khz.  I'd like the infrared device to run continuously at 38khz.

Right now I set the PWM frequency in the variants.h file.  Although this works, it affects all PWM outputs.  Is it possible to set a unique frequency for each PWM output, from within a C++ program?

ralphnev

you have two choices :

A) you can use  PWMC_ConfigureClocks to create two seperate freq clocks
B) you can make 1 clock 38 times slower(faster) than the other ... using  PWMC_SetPeriod
or
C) use timers instead of PWM ...

randomvibe


you have two choices :

A) you can use  PWMC_ConfigureClocks to create two seperate freq clocks
B)...


Do you have a working example with PWMC_ConfigureClocks for the Due?  Is this implemented in the variants.h file or directly in a sketch?

ralphnev

its just a function:

  uint32_t pwm_clk = VARIANT_MCK/2;   //42MHz
  uint16_t pwm_period =  105;           //400kHz==2.5us

  PWMC_ConfigureClocks (pwm_clk, 0, VARIANT_MCK);
  PWMC_SetPeriod (PWM, PWM_CHANNEL_4, pwm_period);

is what i currently  use  ..
if you are trying to get as slow as 1khz  you will likely need to do a VARIANT_MCK/1024 or so
   & adjust your period ..

randomvibe


The SetPeriod function for PWM is very peculiar.

Code: [Select]
void PWMC_SetPeriod( Pwm* pPwm, uint32_t ul_channel, uint16_t period)


Although period can be as large as 65535, it seems to work best at 255.  At lower values, it wrongly increases the PWM duty cycle even though duty is set with PWMC_SetDutyCycle.  At period values much higher than 255, it wrongly decreases duty cycle.

What is the effective range for period?  What is the effective range for clka in the ConfigureClocks function?  Thank you.

Code: [Select]
void PWMC_ConfigureClocks(uint32_t clka, uint32_t clkb, uint32_t mck)

ralphnev

manual says:
      mclk/1024*1/255 is the slowest clock  ==   84000000/1024/255==321.69....HZ
theoreticaly the longest period should be 321.7/65535 =.00491.... hz or 203sec/ pulse...

i dislike the clock setting function / you may have better luck seting it explitistly from the associated values (Find your own dividers & send them to the function )

hook a led(& resistor) to pin 9 & try the code below
Code: [Select]

/*
  This example code is in the public domain.
*/

// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;
#define PWM_CHANNEL_0 0
#define PWM_CHANNEL_4 4
#define PWM_CHANNEL_5 5
#define PWM_CHANNEL_6 6
int period =2;


void
startTimer (Tc * tc, uint32_t channel, IRQn_Type irq, uint32_t frequency)
{
  pmc_set_writeprotect (false);
  pmc_enable_periph_clk ((uint32_t) irq);
  TC_Configure (tc, channel,
TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC |
TC_CMR_TCCLKS_TIMER_CLOCK4);
  uint32_t rc =
    (VARIANT_MCK / 128 / frequency) > 2 ? (VARIANT_MCK / 128 / frequency) : 2;
  //128 because we selected TIMER_CLOCK4 above
  TC_SetRA (tc, channel, rc / 2); //50% high, 50% low
  TC_SetRC (tc, channel, rc);
  TC_Start (tc, channel);
  tc->TC_CHANNEL[channel].TC_IER = TC_IER_CPCS;
  tc->TC_CHANNEL[channel].TC_IDR = ~TC_IER_CPCS;
  NVIC_EnableIRQ (irq);
}
//--------------------------------------------------------------------------
// the setup routine runs once when you press reset:
void setup() {               
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT); 
   Serial.begin(9600);
  startTimer(TC1, 0, TC3_IRQn, 1);
  //PWM config
  pmc_enable_periph_clk(ID_PWM); 
 
  PWMC_DisableChannel(PWM, PWM_CHANNEL_4); 
 
  PWMC_ConfigureClocks(VARIANT_MCK/1024 /255, 0, VARIANT_MCK);

int ulPin = 9;
              // Setup PWM for this pin
              PIO_Configure(g_APinDescription[ulPin].pPort,
              g_APinDescription[ulPin].ulPinType,
              g_APinDescription[ulPin].ulPin,
              g_APinDescription[ulPin].ulPinConfiguration);
   //***************Channel 4           
   PWMC_ConfigureChannel(
     (Pwm*)     PWM,    // pPwm,
     (uint32_t)    PWM_CHANNEL_4  ,  // ul_channel,
     (uint32_t)    PWM_CMR_CPRE_CLKA  ,  // prescaler,
     (uint32_t)    0  ,  // alignment, 0=left alighned
     (uint32_t)    0  ); // polarity )
    PWMC_SetPeriod(
      (Pwm*) PWM,  //pPwm,
      (uint32_t) PWM_CHANNEL_4,//ul_channel,   
      (uint16_t) period
    );
    PWMC_SetDutyCycle(
      (Pwm*) PWM ,//pPwm,
      (uint32_t) PWM_CHANNEL_4,// ul_channel,
      (uint16_t) period/2 //duty
      );
  PWMC_EnableChannel(
        (Pwm*) PWM,  //pPwm,
        (uint32_t)  PWM_CHANNEL_4// ul_channel
      ) ;
}


//*******************************************************8
volatile boolean l;

// This function is called every 1 sec.
void TC3_Handler()
{
  // You must do TC_GetStatus to "accept" interrupt
  // As parameters use the first two parameters used in startTimer (TC1, 0 in this case)
  TC_GetStatus(TC1, 0);

  digitalWrite(led, l = !l);
  period < 4 ?  period = 4:period =period  << 1;
  period >65536 ? period = 4 :period;
      PWMC_SetPeriod(
      (Pwm*) PWM,  //pPwm,
      (uint32_t) PWM_CHANNEL_4,//ul_channel,
      (uint16_t) period-1
    );
      PWMC_SetDutyCycle(
      (Pwm*) PWM ,//pPwm,
      (uint32_t) PWM_CHANNEL_4,// ul_channel,
      (uint16_t) PWM->PWM_CH_NUM[4].PWM_CPRD/2 //duty
      );
}

// the loop routine runs over and over again forever:
void loop() {
Serial.println( PWM->PWM_CH_NUM[4].PWM_CPRD);
 
}

randomvibe

#6
Feb 12, 2013, 09:55 am Last Edit: Feb 12, 2013, 10:02 am by randomvibe Reason: 1
Okay, I finally came up with a clean alternate approach for setting up PWM frequencies from within a sketch, for up to two unique frequencies.  The trick is to utilize the two PWM clocks (CLKA & CLKB) provided by the SAM3X8E chip.

I wrote and enclosed a library (pwm01.h).  It includes 4 user functions to:  1) setup PWM resolution, 2) setup PWM pin, frequency & pick clock, 3) write duty cycle, and 4) stop PWM.  See example code for usage:

Code: [Select]
#include "C:\Programs\arduino-1.5.1r2\hardware\arduino\sam\libraries\Pwm01\pwm01.h"

void setup()
{
   uint32_t  pwm_duty = 32767;
   uint32_t  pwm_freq1 = 2;  
   uint32_t  pwm_freq2 = 5000;

   // Set PWM Resolution
   pwm_set_resolution(16);  

   // Setup PWM Once (Up to two unique frequencies allowed
   //-----------------------------------------------------    
   pwm_setup( 6, pwm_freq1, 1);  // Pin 6 freq set to "pwm_freq1" on clock A
   pwm_setup( 7, pwm_freq2, 2);  // Pin 7 freq set to "pwm_freq2" on clock B
   pwm_setup( 8, pwm_freq2, 2);  // Pin 8 freq set to "pwm_freq2" on clock B
   pwm_setup( 9, pwm_freq2, 2);  // Pin 9 freq set to "pwm_freq2" on clock B
     
   // Write PWM Duty Cycle Anytime After PWM Setup
   //-----------------------------------------------------    
   pwm_write_duty( 6, pwm_duty );  // 50% duty cycle on Pin 6
   pwm_write_duty( 7, pwm_duty );  // 50% duty cycle on Pin 7
   pwm_write_duty( 8, pwm_duty );  // 50% duty cycle on Pin 8
   pwm_write_duty( 9, pwm_duty );  // 50% duty cycle on Pin 9

   delay(30000);  // 30sec Delay; PWM signal will still stream
       
   // Force PWM Stop On All Pins
   //-----------------------------    
   pwm_stop( 6 );
   pwm_stop( 7 );
   pwm_stop( 8 );
   pwm_stop( 9 );
}

void loop()
{  
}



The pwm01.h library and example code were tested in IDE 1.5.1r2.  Additional notes on this library:
- Applies to Arduino-Due board, PWM pins 6, 7, 8 & 9.
- Libary Does not operate on the TIO pins.
- Unique frequencies set via PWM Clock-A ("CLKA") and Clock-B ("CLKB")
 Therefore, up to two unique frequencies allowed.
- Set max duty cycle counts (pwm_max_duty_Ncount) equal to 255 per Arduino approach.  
 This value is best SUITED for low frequency applications (2hz to 40,000hz) such as
 PWM motor drivers, 38khz infrared transmitters, etc.
- Future library versions will address high frequency applications.
- Arduino's "wiring_analog.c" function was very helpful in this effort.

dishimwe


Okay, I finally came up with a clean alternate approach for setting up PWM frequencies from within a sketch, for up to two unique frequencies.  The trick is to utilize the two PWM clocks (CLKA & CLKB) provided by the SAM3X8E chip.

I wrote and enclosed a library (pwm01.h).  It includes 4 user functions to:  1) setup PWM resolution, 2) setup PWM pin, frequency & pick clock, 3) write duty cycle, and 4) stop PWM.  See example code for usage:

The pwm01.h library and example code were tested in IDE 1.5.1r2.  Additional notes on this library:
- Applies to Arduino-Due board, PWM pins 6, 7, 8 & 9.


Do you know how use that to get higher frequencies like 25MHz?

ralphnev


Do you know how use that to get higher frequencies like 25MHz?

not possible
must beable to divide 84MHz by an integer so the limits are
42MHz , 28, 21, 16.8, 14, 12, 10.5 , ....

dishimwe

I have been using the pmc but i cannot get a clean square wave on higher frequencies, how can i use the PMW to generate 21MHz?

vorkiej

#10
Mar 29, 2013, 10:56 am Last Edit: Mar 29, 2013, 01:56 pm by vorkiej Reason: 1
8-bit resolution gives you a maximum PWM frequency of 84000000 Hz (MCK) / 255 = 329411 Hz
6-bit resolution a max of 84Mhz/63 = 1,3Mhz etc..


MickD

#11
Apr 18, 2013, 10:40 am Last Edit: Apr 29, 2013, 08:53 am by MickD Reason: 1
Hiya all,

I have been working on something along the same lines. I was hoping to build a function to set 1 or more of the three timers to produce a clock pulse of 100Hz to 2MHz. Built a library and a function using the AT91 and sam3x8w datasheets provided by Atmel.

Register_Deff.h
Code: [Select]

//Software

//Define control registers locations.

///////////////////////////////////////////////////////////////////
// Timers
///////////////////////////////////////////////////////////////////

//For Timer 1


#define TC1_CCR ((volatile unsigned int *) 0x40080000)
#define TC1_CMR ((volatile unsigned int *) 0x40080004)
#define TC1_RA ((volatile unsigned int *) 0x40080014)
#define TC1_RB ((volatile unsigned int *) 0x40080018)
#define TC1_RC ((volatile unsigned int *) 0xFFFE005C)


//For Timer 2

#define TC2_CCR ((volatile unsigned int *) 0x40080040)
#define TC2_CMR ((volatile unsigned int *) 0x40080044)
#define TC2_RA ((volatile unsigned int *) 0x40080054)
#define TC2_RB ((volatile unsigned int *) 0x40080058)
#define TC2_RC ((volatile unsigned int *) 0x4008005C)



//For Timer 3

#define TC3_CCR ((volatile unsigned int *) 0x40080080)
#define TC3_CMR ((volatile unsigned int *) 0x40080084)
#define TC3_RA ((volatile unsigned int *) 0x40080094)
#define TC3_RB ((volatile unsigned int *) 0x40080098)
#define TC3_RC ((volatile unsigned int *) 0x4008009C)


////////////////////////////////////////////////////////////////////
// General Ports
////////////////////////////////////////////////////////////////////

#define PortA_DIR ((volatile unsigned int *) 0x400E0E04)
#define PORTB_DIR ((volatile unsigned int *) 0x400E1004)
#define PORTC_DIR ((volatile unsigned int *) 0x400E1204)
#define PORTD_DIR ((volatile unsigned int *) 0x400E1404)
#define PORTE_DIR ((volatile unsigned int *) 0x400E1604)
#define PORTF_DIR ((volatile unsigned int *) 0x400E1804)






Function
Code: [Select]

int Pulse(Timer_ID, Frequency, Pulse_Width)
{
/*
This function will put out two PWM signals out of TIOAx (Desired pulse width),TIOBx (Always set to 50%). This is a hardware function, meaning once set it will have no effect on the external program.
Unless the external program trys to use the timer that has been initialised here
*/


//* TC_CMR: Timer Counter Channel Mode Register Bits Definition


unsigned int
TC_CLKS_MCK2 = 0x0,
TC_EEVT_XC0 = 0x400,
TC_CPCTRG = 0x4000,
TC_WAVE = 0x8000,
TC_ACPA_TOGGLE_OUTPUT = 0x30000,
TC_ACPC_TOGGLE_OUTPUT = 0xC0000,
TC_ASWTRG_SET_OUTPUT = 0x400000,
TC_BCPB_TOGGLE_OUTPUT = 0x3000000,
TC_BCPC_TOGGLE_OUTPUT = 0xC000000,
TC_BSWTRG_SET_OUTPUT = 0x40000000;

//* TC_CCR: Timer Counter Control Register Bits Definition
unsigned int
TC_CLKEN = 0x1,
TC_CLKDIS = 0x2,
TC_SWTRG = 0x4;


// Porting
int
PIOTIOA1 = 4, // Timer 1 Signal A
PIOTIOB1 = 5; // Timer 1 Signal B


*TC1_CCR = TC_CLKDIS ; // Disable the Clock Counter


*PortA_DIR = (1<<PIOTIOA1) | (1<<PIOTIOB1) ; // Define TIOA1 and TIOB1 as peripheral


//* Compare registers initialization
RC_Value = (42000000)/Frequency ; /* MCK/2/Frequency =>PWM generation Frequency */
RB_Value= RC_Value/2; /* 50% duty cycle on TIOB1 */
RA_Value = RC_Value*Pulse_Width; /* Desired Duty Cycle on TIOA1 */


//Timer Select

switch (Timer_ID)
{

case 1:
*TC1_CMR =
TC_BSWTRG_SET_OUTPUT | /* BSWTRG : software trigger set TIOB */
TC_BCPC_TOGGLE_OUTPUT | /* BCPC : Register C compare toggle TIOB */
TC_BCPB_TOGGLE_OUTPUT | /* BCPB : Register B compare toggle TIOB */
TC_ASWTRG_SET_OUTPUT | /* ASWTRG : software trigger set TIOA */
TC_ACPC_TOGGLE_OUTPUT | /* ACPC : Register C compare toggle TIOA */
TC_ACPA_TOGGLE_OUTPUT | /* ACPA : Register A compare toggle TIOA */
TC_WAVE | /* WAVE : Waveform mode */
TC_CPCTRG | /* CPCTRG : Register C compare trigger enable */
TC_EEVT_XC0 | /* EEVT : XC0 as external event (TIOB=output) */
TC_CLKS_MCK2 ; /* TCCLKS : MCK / 2 */

*TC1_RC = RC_Value ; /* PWM generation */
*TC1_RB = RB_Value ; /* duty cycle on TIOB1 */
*TC1_RA = RA_Value ; /* duty cycle on TIOA1 */


    break;

case 2:
*TC2_CMR=
TC_BSWTRG_SET_OUTPUT | /* BSWTRG : software trigger set TIOB */
TC_BCPC_TOGGLE_OUTPUT | /* BCPC : Register C compare toggle TIOB */
TC_BCPB_TOGGLE_OUTPUT | /* BCPB : Register B compare toggle TIOB */
TC_ASWTRG_SET_OUTPUT | /* ASWTRG : software trigger set TIOA */
TC_ACPC_TOGGLE_OUTPUT | /* ACPC : Register C compare toggle TIOA */
TC_ACPA_TOGGLE_OUTPUT | /* ACPA : Register A compare toggle TIOA */
TC_WAVE | /* WAVE : Waveform mode */
TC_CPCTRG | /* CPCTRG : Register C compare trigger enable */
TC_EEVT_XC0 | /* EEVT : XC0 as external event (TIOB=output) */
TC_CLKS_MCK2 ; /* TCCLKS : MCK / 2 */

*TC2_RC = RC_Value ;
*TC2_RB = RB_Value ;
*TC2_RA = RA_Value ;

    break;

case 3:
*TC3_CMR=
TC_BSWTRG_SET_OUTPUT | /* BSWTRG : software trigger set TIOB */
TC_BCPC_TOGGLE_OUTPUT | /* BCPC : Register C compare toggle TIOB */
TC_BCPB_TOGGLE_OUTPUT | /* BCPB : Register B compare toggle TIOB */
TC_ASWTRG_SET_OUTPUT | /* ASWTRG : software trigger set TIOA */
TC_ACPC_TOGGLE_OUTPUT | /* ACPC : Register C compare toggle TIOA */
TC_ACPA_TOGGLE_OUTPUT | /* ACPA : Register A compare toggle TIOA */
TC_WAVE | /* WAVE : Waveform mode */
TC_CPCTRG | /* CPCTRG : Register C compare trigger enable */
TC_EEVT_XC0 | /* EEVT : XC0 as external event (TIOB=output) */
TC_CLKS_MCK2 ; /* TCCLKS : MCK / 2 */

*TC3_RC = RC_Value ;
*TC3_RB = RB_Value ;
*TC3_RA = RA_Value ;

    break;
default:


    break;
}



//Turn it all on
*TC1_CCR = TC_CLKEN ; /* Enable the Clock counter */
*TC1_CCR = TC_SWTRG ; /* Trig the timer */
}


Though I ran into a couple of problems:
-Everyone's code I have seen so far has been using 16 bit timers for the AT91 (including Atmel's), though by the looks of the sam3x8e data sheet it has 32 bit registers (timer and compare registers). This would explain the constant high output I am getting.

The Due should be able to produce hardware driven PWM frequencies into the MHZ (havn't seen any posts on it yet), even if they are not perfectly clean. Can someone please point me in the right direction, is the coding or my text book reading wrong.

exedor

#12
Jun 18, 2013, 09:34 am Last Edit: Jun 18, 2013, 11:54 pm by exedor Reason: 1
For what it is worth, I formalized this library into an actual object and one that no longer requires putting the entire path to the thing in the include statement as long as it is located in one of the standard library directories.

It doesn't incorporate the timer code posted later to be run a timer on the timer clocks.  That might be worth doing as well as doing the high speed PWM feature randomvibe was talking about earlier.

sandrograssia

#13
Aug 15, 2013, 12:43 am Last Edit: Aug 15, 2013, 12:47 am by sandrograssia Reason: 1

Okay, I finally came up with a clean alternate approach for setting up PWM frequencies from within a sketch, for up to two unique frequencies.  The trick is to utilize the two PWM clocks (CLKA & CLKB) provided by the SAM3X8E chip.

I wrote and enclosed a library (pwm01.h).  It includes 4 user functions to:  1) setup PWM resolution, 2) setup PWM pin, frequency & pick clock, 3) write duty cycle, and 4) stop PWM.  See example code for usage:

Code: [Select]
#include "C:\Programs\arduino-1.5.1r2\hardware\arduino\sam\libraries\Pwm01\pwm01.h"

void setup()
{
   uint32_t  pwm_duty = 32767;
   uint32_t  pwm_freq1 = 2;  
   uint32_t  pwm_freq2 = 5000;

   // Set PWM Resolution
   pwm_set_resolution(16);  

   // Setup PWM Once (Up to two unique frequencies allowed
   //-----------------------------------------------------    
   pwm_setup( 6, pwm_freq1, 1);  // Pin 6 freq set to "pwm_freq1" on clock A
   pwm_setup( 7, pwm_freq2, 2);  // Pin 7 freq set to "pwm_freq2" on clock B
   pwm_setup( 8, pwm_freq2, 2);  // Pin 8 freq set to "pwm_freq2" on clock B
   pwm_setup( 9, pwm_freq2, 2);  // Pin 9 freq set to "pwm_freq2" on clock B
     
   // Write PWM Duty Cycle Anytime After PWM Setup
   //-----------------------------------------------------    
   pwm_write_duty( 6, pwm_duty );  // 50% duty cycle on Pin 6
   pwm_write_duty( 7, pwm_duty );  // 50% duty cycle on Pin 7
   pwm_write_duty( 8, pwm_duty );  // 50% duty cycle on Pin 8
   pwm_write_duty( 9, pwm_duty );  // 50% duty cycle on Pin 9

   delay(30000);  // 30sec Delay; PWM signal will still stream
       
   // Force PWM Stop On All Pins
   //-----------------------------    
   pwm_stop( 6 );
   pwm_stop( 7 );
   pwm_stop( 8 );
   pwm_stop( 9 );
}

void loop()
{  
}



The pwm01.h library and example code were tested in IDE 1.5.1r2.  Additional notes on this library:
- Applies to Arduino-Due board, PWM pins 6, 7, 8 & 9.
- Libary Does not operate on the TIO pins.
- Unique frequencies set via PWM Clock-A ("CLKA") and Clock-B ("CLKB")
 Therefore, up to two unique frequencies allowed.
- Set max duty cycle counts (pwm_max_duty_Ncount) equal to 255 per Arduino approach.  
 This value is best SUITED for low frequency applications (2hz to 40,000hz) such as
 PWM motor drivers, 38khz infrared transmitters, etc.
- Future library versions will address high frequency applications.
- Arduino's "wiring_analog.c" function was very helpful in this effort.



Dear randomvibe, I'm grateful for this library. I'm testing it on my Arduino Due, it works great but I'm facing an issue about frequency; for example, if I set:
uint32_t pwm_freq1=1000;
then on pin 6 my (old) oscilloscope measures more or less 200Hz. So: either my oscilloscope is too old, or I'm missing something...
Thanks

the_ether


Dear randomvibe, I'm grateful for this library. I'm testing it on my Arduino Due, it works great but I'm facing an issue about frequency; for example, if I set:
uint32_t pwm_freq1=1000;
then on pin 6 my (old) oscilloscope measures more or less 200Hz. So: either my oscilloscope is too old, or I'm missing something...
Thanks


Did this issue get resolved?

Go Up