Go Down

Topic: PWM with variable Frequency and Duty Cycle (Read 7409 times) previous topic - next topic

Antrix

Hi, I'm trying to generate PWM with the Arduino Pro Mini 16Mhz. There is another discussion that uses Arduino Mega to achive something like this using Registers. I tried to follow that discussion, however it is more complex for my level of programming.

The original PWM library example discussed in that thread uses 'delay' which I can't afford in my project. So I went with NewTone to generate a frequency in the following manner:

Code: [Select]

#include <NewTone.h>

const byte pulsePin = 13;

void setup() {
  Serial.begin(115200);

}

void loop() {
  NewTone(pulsePin, 1);
}


What the project needs is to have a known frequency generated through an output pin. Here I have used Pin 13 since it has an LED attached which functions as a visual feedback. With the code above, I can generate a frequency but the duty cycle is fixed at 50%. Is there a way to change the duty cycle? For further development, I plan to use a keypad to input frequency and use a pot or perhaps rotary encoder to vary duty cycle.

Any help would be appreciated!

6v6gt

Here is an example of a variable frequency / duty cycle for an ATMEGA328p.
The output is fixed on pin 3. Maybe you can adapt it to suit your frequency/duty cycle requirements.


Code: [Select]

//
//  38kHz to atmega328p D3
//  based on https://gist.github.com/chendy/69454f6ec77b54f9a710
//
//


byte oscOut = 3 ;  // Atmega328p  OC2B=D3 ;

void setup(){
 
  pinMode(oscOut, OUTPUT);

  OCR2A = 51;     // defines the frequency 51 = 38.4 KHz, 54 = 36.2 KHz, 58 = 34 KHz, 62 = 32 KHz
  OCR2B = 26;     // defines the duty cycle - Half the OCR2A value for 50%
  TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);   // COM2B1 (output to OC2B) ; WGMode 7 Fast PWM (part 1)
  TCCR2B = _BV(WGM22)  | _BV(CS21);                 // prescalere x8 ;  WGMode 7 Fast PWM (part 1)
}

void loop(){ }

Antrix

#2
Mar 03, 2018, 08:36 pm Last Edit: Mar 03, 2018, 08:42 pm by Antrix
Here is an example of a variable frequency / duty cycle for an ATMEGA328p.
The output is fixed on pin 3. Maybe you can adapt it to suit your frequency/duty cycle requirements.


Code: [Select]

//
//  38kHz to atmega328p D3
//  based on https://gist.github.com/chendy/69454f6ec77b54f9a710
//
//


byte oscOut = 3 ;  // Atmega328p  OC2B=D3 ;

void setup(){
 
  pinMode(oscOut, OUTPUT);

  OCR2A = 51;     // defines the frequency 51 = 38.4 KHz, 54 = 36.2 KHz, 58 = 34 KHz, 62 = 32 KHz
  OCR2B = 26;     // defines the duty cycle - Half the OCR2A value for 50%
  TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);   // COM2B1 (output to OC2B) ; WGMode 7 Fast PWM (part 1)
  TCCR2B = _BV(WGM22)  | _BV(CS21);                 // prescalere x8 ;  WGMode 7 Fast PWM (part 1)
}

void loop(){ }


Thank you 6v6gt for your consideration. I tried the code with an LED (with resistor) attached at Pin 3 and ground on the Arduino Pro Mini. The example states 51=38.4 KiloHertz. This would be way off the charts for my project. I need to have something more modest in the range of 1-5000Hz.

6v6gt

At around 38kHz, you wont see a flicker, but by altering the duty cycle, you'll see the led brightness change.

With the code example I gave, you can, by changing the prescaler to 1024, get a frequency of between about 60 Hz and 15 Khz but the duty cycle adjustment may not be very granular, especially at the extremities of the range. See the data sheet for which registers you use for this.

What range of duty cycles do you want to use ? 

Antrix

Thank you,

Yes, I tried the duty cycle option and the brightness is fairly reduced. However, If I could select any frequency between 1-5000Hz, this would indeed solve my problem. Please don't mind if certain things don't seem obvious to me due to lack of knowledge in this area.

I need 50% and less in terms of duty cycle. I found this code online that generates a 30Hz pulse, but I don't know how to modify the frequency to any arbitrary value. But then, it misses out on duty cycle.

Code: [Select]

void setup() {
pinMode(3, OUTPUT);
pinMode(11,OUTPUT);
TCCR2A=0;//reset the register
TCCR2B=0;//reset the register
TCNT2=0;
TCCR2A=0b01010010;//COM2A1, COM2B1 are 0, COM2A0, COM2B0 are 1
//also WGM21 is 1
TCCR2B=0b00000111;//WGM22 is 0 with 1024 prescaler
OCR2A=255;// compare match value
}
void loop() {
// put your main code here, to run repeatedly
}

cattledog

Take a look at the TimerOne library. There is a pwm function for variable frequency and duty cycle. With the promini, Output will be on pins 9 and 10.
https://www.pjrc.com/teensy/td_libs_TimerOne.html

Antrix

Take a look at the TimerOne library. There is a pwm function for variable frequency and duty cycle. With the promini, Output will be on pins 9 and 10.
https://www.pjrc.com/teensy/td_libs_TimerOne.html
Thanks a lot for pointing out this amazing library! With it I was able to produce a pulsed signal of 1Hz:
Code: [Select]

#include <TimerOne.h>

const int pulsePin = 9;

void setup(void)
{
  Timer1.initialize(1000000);  // 40 us = 25 kHz
  Serial.begin(9600);
}

void loop(void)
{
  Timer1.pwm(pulsePin, 1);
}


I understand that the frequency could be set with 'Timer1.initialize(1000000)', but how can to generate any arbitrary frequency. For example with Arduino Tone library, the frequency is directly passed on as an argument to the tone function. So if, I need to generate a 20Hz frequency, what should I change in the code above?

I think it's worth mentioning that I would use a keypad to directly input the required frequency.

A little help would be great here!

cattledog

Quote
So if, I need to generate a 20Hz frequency, what should I change in the code above?
The period is 1/frequency. For 20 Hz. the period is 1/20  =  50 milliseconds.

Code: [Select]
Timer1.initialize(50000);//20 Hz frequency 50 ms period

The documentation on the previously linked page is pretty complete. What specific questions do you have?

Quote
Configuration
Timer1.initialize(microseconds);
Begin using the timer. This function must be called first. "microseconds" is the period of time the timer takes.

Timer1.setPeriod(microseconds);
Set a new period after the library is already initialized.

Run Control
Timer1.start();
Start the timer, beginning a new period.

Timer1.stop();
Stop the timer.

Timer1.restart();
Restart the timer, from the beginning of a new period.

Timer1.resume();
Resume running a stopped timer. A new period is not begun.

PWM Signal Output
Timer1.pwm(pin, duty);
Configure one of the timer's PWM pins. "duty" is from 0 to 1023, where 0 makes the pin always LOW and 1023 makes the pin always HIGH.

Timer1.setPwmDuty(pin, duty);
Set a new PWM, without reconfiguring the pin. This is slightly faster than pwm(), but pwm() must be used at least once to configure the pin.

Timer1.disablePwm(pin);
Stop using PWM on a pin. The pin reverts to being controlled by digitalWrite().

Antrix

The period is 1/frequency. For 20 Hz. the period is 1/20  =  50 milliseconds.

Code: [Select]
Timer1.initialize(50000);//20 Hz frequency 50 ms period

The documentation on the previously linked page is pretty complete. What specific questions do you have?
Thanks for simplifying the matter of converting numeric value into frequency. I managed to do that with the code below taking cues from your example, please take a look to see if anything could be improved.

Code: [Select]

#include <TimerOne.h>

const int pulsePin = 9;
float inputFreq = 20;
float Freq;

void setup(void)
{
  Freq = (1/inputFreq)*1000000;
  Timer1.initialize(Freq);
  Serial.begin(9600);
}

void loop(void)
{
  Serial.println(Freq);
  Timer1.pwm(pulsePin, 40);
}


inputFreq is the intended frequency to be generated and Freq is the converted form.

Thank you once again!

cattledog

#9
Mar 04, 2018, 06:09 pm Last Edit: Mar 04, 2018, 06:09 pm by cattledog
I would clarify your terminology and use "frequency" and "period".  Do a little searching on these terms to understand.

Change the name of your variable  Freq to Period. It should be typed as unsigned long since it is a time value in microseconds.

inputFreq can be typed as an int if your max value is 5000Hz.

Be aware, that when using the TimerOne library, the values used for duty cycle range from 0 to 1023. You will need to do some math if you are trying to use a percentage for duty cycle.

Code: [Select]

#include <TimerOne.h>

const int pulsePin = 9;
int inputFreq = 20;//Hz
unsigned long period;//microseconds

void setup(void)
{
  //Freq = (1/inputFreq)*1000000;
  period = 1000000/inputFreq;
  Timer1.initialize(period);
  Serial.begin(9600);
}

void loop(void)
{
  Serial.println(period);
  Timer1.pwm(pulsePin, 40);//gives duty cycle of 3.9%
}



Antrix

#10
Mar 04, 2018, 07:17 pm Last Edit: Mar 04, 2018, 07:59 pm by Antrix Reason: Update
I would clarify your terminology and use "frequency" and "period".  Do a little searching on these terms to understand.

Change the name of your variable  Freq to Period. It should be typed as unsigned long since it is a time value in microseconds.

inputFreq can be typed as an int if your max value is 5000Hz.

Be aware, that when using the TimerOne library, the values used for duty cycle range from 0 to 1023. You will need to do some math if you are trying to use a percentage for duty cycle.
...
Thank you for the suggestion. I primed my understanding of frequency and it's inversely proprtional relation to period. Yes, now that's clear. About duty cycle, the primary objective is varying the intensity of a pulsing laser diode, accuracy won't be crucial here, just the ability to vary the intensity is more important.

Meanwhile, in an effort to evolve the project, I tried to implement the 4x4 keypad code into it. I must've made some mistake again because the serial output is not somewhat favourable. Below is the code and the serial output:


After some further efforts, the code works as intended. Below is the final code for generating pulsed output with varying duty cycle:

Code: [Select]

#include <Keypad.h>
#include <TimerOne.h>

const int pulsePin = 9;
unsigned long period;//microseconds
int inputFreq;

int kdelay = 50;
const byte ROWS = 4;
const byte COLS = 4;
char hexaKeys[COLS][ROWS] =
{
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

byte rowPins[ROWS] = {11, 10, 12, 8}; //Rows 0 to 4
byte colPins[COLS] = {7, 6, 5, 4}; //Columns 0 to 4
Keypad kpd = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
String v1;

void setup() {
  Serial.begin(115200);
}

void loop() {

  v1 = GetNumber();
  v1.toInt();
  long inputFreq = v1.toInt();
  period = 1000000 / inputFreq;
  Timer1.initialize(period);
  Timer1.pwm(pulsePin, 40);//gives duty cycle of 3.9%

  Serial.print("Period: "); Serial.println(period);
  Serial.print("inputFreq: "); Serial.println(v1.toInt());
}

String GetNumber()
{
  String num;
  char key = kpd.getKey();
  while (key != '#')
  {
    switch (key)
    {
      case NO_KEY:
        break;

      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':

        num = num + key;
        break;

      case '*':
        num = "";
        break;
    }
    key = kpd.getKey();
  }
  return num;
}


Thanks a lot to everyone who has helped me in this process.

subho7deep

Here is an example of a variable frequency / duty cycle for an ATMEGA328p.
The output is fixed on pin 3. Maybe you can adapt it to suit your frequency/duty cycle requirements.


Code: [Select]

//
//  38kHz to atmega328p D3
//  based on https://gist.github.com/chendy/69454f6ec77b54f9a710
//
//


byte oscOut = 3 ;  // Atmega328p  OC2B=D3 ;

void setup(){
 
  pinMode(oscOut, OUTPUT);

  OCR2A = 51;     // defines the frequency 51 = 38.4 KHz, 54 = 36.2 KHz, 58 = 34 KHz, 62 = 32 KHz
  OCR2B = 26;     // defines the duty cycle - Half the OCR2A value for 50%
  TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);   // COM2B1 (output to OC2B) ; WGMode 7 Fast PWM (part 1)
  TCCR2B = _BV(WGM22)  | _BV(CS21);                 // prescalere x8 ;  WGMode 7 Fast PWM (part 1)
}

void loop(){ }


Can this code be used on arduino UNO with frequency of 25kHz?
and what will be changes if it is possible?

6v6gt

Yes. Set OCR2A = 80 for 25 kHz and, if you want a 50% duty cycle, OCR2B = 40 ;

Go Up