Generating a Ramp-Up PWM using Arduino Mega's Timer (hardware-based)

Hi all,
I have been successfully managed to run my stepper motor with a software based PWM that uses millis() to initially ramp-up my HIGH-LOW logic and then after a certain numbers of steps reaching a constant speed.
However, since this ramp-up logic is part of a more complex code, it may potentially clash when increasing the complexity of the code (reading more sensors / running more actuators). Therefore, I have decided to migrate to an hardware based PWM (I am using Arduino Mega - thought about using Timer 3, 4 or 5) that should ramp up for initially x steps (I am currently using 200 steps to go from 600 Hz up to 2.5 kHz) till reaching my constant frequency (i.e. speed)

I wrote this code that will generate a constant PWM on pin 5 (Timer 3 based):

  int myEraserPulses = 7;             // this is 111 in binary and is used as an eraser
  TCCR3B &= ~myEraserPulses;   //   set the three bits in TCCR2B to 0
  TCCR3A = _BV(COM3A0) | _BV(WGM31) | _BV(WGM30); // fast PWM
  TCCR3B = _BV(WGM33) | _BV(WGM32); // 16MHz/8=2MHz
  OCR3A =  400;          // 2MHz/2.5kHz=800 (50% duty--> 400)
  TCNT3 = 0;
  TCCR3B |= _BV(CS31); //start timer
  GTCCR = 0;

Do you have any advice on how to adjust this code to generate an initial ramping up part?

No one? :frowning:

Here's what I would do

Enable overflow interrupt on your timer. On the ISR, increment/decrement your TOP value to change the PWM frequency. In this case, you have to use a different PWM mode (Not fast PWM) so you can change the top value. Look at the datasheet how to configure OCR3A as the TOP value, and have OCR3B defines the duty cycle.

You can disable the ISR once you achieve your highest PWM frequency

The hardware for PWM has no support for ramping, the best you could do is adjust it every cycle by using an interrupt triggered by the PWM unit.

Have you looked at how the AccelStepper library works?

Or have you considered just using it?

Thanks! It looks like a good idea

@hzrnbgy I have implemented the following solution using Arduino Mega Timer 5 - output pin 46

byte PWM=46;
volatile int i=0;
int incomingByte;
int starting_frequency=832;

void setup() {

Serial.begin(9600);
pinMode (PWM, OUTPUT); // set the pulses pin as output

noInterrupts();
int myEraserPulses = 7;             // this is 111 in binary and is used as an eraser
TCCR5B &= ~myEraserPulses;   // this operation (AND plus NOT),  set the three bits in TCCR2B to 0
TCCR5A = _BV(COM5A0) |  _BV(WGM52) | _BV(WGM50); // fast PWM
TCCR5B = _BV(WGM53); // 16MHz/8=2MHz
TIMSK5 = 0b00000001; // overflow interrupt enable
OCR5A =  starting_frequency; //starting_frequency 600 Hz;
TCNT5 = 0;
TCCR5B |= _BV(CS51); //start timer
GTCCR = 0;
interrupts();
}

ISR(TIMER5_OVF_vect){ 
OCR5A=OCR5A-2;
  if(OCR5A==200){ // final frequency 2.5 kHz
  TIMSK5 = 0b00000000;
  }
}

void loop() {
}

It is working properly. It starts at 600 Hz and after 150 ms it goes up to 2.5 kHz. Do you think it is a proper solution or do you foresee any risk / any more stylish change?

WGM should be 1111 as shown here (yours is 1101)

And it should be

TIMSK5 = 1<<OCIE5A

since OCR5A is now your TOP

It's "working" but not as you'd expect or intended.

Using WGM 1111

the TOP value of the counter is OCR5A, and you use OCF5A compare ISR to update the value of OCR5A

One of the remaining output channels (OCR5B or OCR5C) determines the duty cycle.

And it should be
TIMSK5 = 1<<OCIE5A

It is basically what I am doing with this line: TIMSK5 = 0b00000001; right?! OCIE5A is the last bit of the TIMSK5 register

since OCR5A is now your TOP
It's "working" but not as you'd expect or intended.
Using WGM 1111

I have updated the code for FAST PWM mode, sorry I thought it won't work with WGM 111 (in your first message you said:

you have to use a different PWM mode (Not fast PWM) so you can change the top value.

Code Updated:

void setup() {

Serial.begin(9600);
pinMode (PWM, OUTPUT); // set the pulses pin as output

noInterrupts();
int myEraserPulses = 7;             // this is 111 in binary and is used as an eraser
TCCR5B &= ~myEraserPulses;   // this operation (AND plus NOT),  set the three bits in TCCR2B to 0
TCCR5A = _BV(COM5A0) |  _BV(WGM51) | _BV(WGM50); // fast PWM
TCCR5B = _BV(WGM52) | _BV(WGM53); // 16MHz/8=2MHz
TIMSK5 = 0b00000001; // overflow interrupt enable
OCR5A =  1666; // 2MHz / 600 Hz= 3333 (50% duty --> 1666.6)
TCNT5 = 0;
TCCR5B |= _BV(CS51); //start timer
GTCCR = 0;
interrupts();
}

ISR(TIMER5_OVF_vect){ 
OCR5A=OCR5A-2;
  if(OCR5A==398){ // final frequency 2.5 kHz: 2MHz / 2500 Hz= 800 (50% duty --> 400)
  TIMSK5 = 0b00000000;
  }
}

About your last sentence:

and you use OCF5A compare ISR to update the value of OCR5A
One of the remaining output channels (OCR5B or OCR5C) determines the duty cycle.

I honestly don' get what you mean. Are you probably suggesting instead of subtracting 2 to OCR5A each overflow, to update OCR5A by comparing the overflow with OCF5A ?
Also, I am already getting 50% duty cycle that for me is fine, so I don't need to change it.

@hzrnbgy ?

Here's the basic idea

Set timer5 to Fast PWM with TOP at OCR5A. Depending on the value of the pre-scalers (the CS bits), the counter will count from zero to the value OCR5A. For example, if the mega is running at 16MHz and you set the prescaler at 64, your counter will count at (16 000 000 / 64) or 250 kHz.

Now if you set OCR5A to 50 000, the 250 kHz counter will count from zero to 50 000, reset to zero, and so on.

As you can see, if you count at a rate of 250 kHz and you only need to count up to 50 000, you end up hitting the 50 000 mark five times a seconds. That is your PWM frequency (5 Hz). If you enable the COMPA interrupt, you'll execute the interrupt 5 times a seconds. You can then change the value of OCR5A inside this interrupt so you can increase/decrease the frequency of the PWM. The value of OCR5B and OCR5C represent the duty cycle of the PWM. If you set it to half of the TOP value, you always get a 50% duty cycle.

volatile uint16_t TOP = 0;

ISR(TIMER5_COMPA_vect)
{
	// increasing TOP means decreasing the frequency of the PWM
	TOP++;
	
	// this is so the duty cycle is always approximately 50%
	OCR5B = TOP >> 1;
	OCR5C = TOP >> 1;
}


setup()
{
// setup timer
// fast PWM with TOP at OCR5A (WGM 1111)
TCCR5A = 1<<COM5B1 | 0<<COM5B0 | 1<<COM5C1 | 0<<COM5C0 | 1<<WGM51 | 1<<WGM50;
TCCR5B = 1<<WGM53 | 1<<WGM52;

// set TOP value, defines initial frequency of PWM
TOP = 100;
OCR5A = TOP;

// set 50% duty cycle
OCR5B = TOP >> 1;
OCR5C = TOP >> 1;

// enable OCIE5A interrupt
TIMSK5 = 1<<OCIE5A;

// finally start timer (change to desired pre-scaler)
TCCR5B |= 0<<CS52 | 1<<CS51 | 1<<CS50;
}

The outputs are routed to where OCR5B and OCR5C is connected to. Also, don't forget to enable these pins as output using pinMode or something.

It is basically what I am doing with this line: TIMSK5 = 0b00000001; right?! OCIE5A is the last bit of the TIMSK5 register

TOIE is the last bit and not OCIE (your bible for these kind of things is the datasheet)

Also, you should start using the 1<<OCIE5A notation instead of the 0b0000010 as it's easier to read

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.