Smoothly changing the frequency of a timer

Hi,

I'm having a problem with a proof of concept code snippet which, when functional, will form the basis of a bigger project. I am using Timer1 on the Mega 2560 to produce a stream of pulses which drive a stepper motor. I want to gradually increase the pulse rate to some maximum value and then gradually decrease it again. I am using the following code to achieve this:

int LEDPIN=13;

void setup()
{
    pinMode(13, OUTPUT);
 
   // initialize Timer1
    cli();                   // disable global interrupts
    TCCR1A = 0;     // set entire TCCR1A register to 0
    TCCR1B = 0;     // same for TCCR1B
 
    TCCR1B |= (1 << WGM12);     // turn on CTC mode
  
    OCR1A = 1000;    // set compare match register to some large(ish) value 
  
    // Set prescaler:
    TCCR1B |= (1 << CS10);
    TCCR1B |= (1 << CS11);

    TIMSK1 |= (1 << OCIE1A);    // enable timer compare interrupt
    sei();          // enable global interrupts
}

ISR(TIMER1_COMPA_vect)
{
    digitalWrite(LEDPIN,HIGH);
    digitalWrite(LEDPIN,LOW);
}

//Smoothly ramp up and down pulse speed in the main loop
void loop(){
  
  int d=5000;      //Delay in microseconds between each update of OCR1A 
  int MX=2000;  //Maximum value of OCR1A
  int MN=50;      //Minimum value of OCR1A
  
 
  for (int ii=MX; ii>MN; ii--){ 
     OCR1A=ii; 
     delayMicroseconds(d); 
  }
  for (int ii=MN; ii<MX; ii++){
     OCR1A=ii;
     delayMicroseconds(d); 
  }
}

This almost works. The problem I have is that the signal becomes choppy at times. I can see the choppiness both in the motion of the the stepper motor and examining the pulse train with a scope. I suspect the problem lies in the timer's counter sometimes wrapping around when the value of OCR1A changes. I'm not sure what would be the way to fix this or whether there is a better solution to achieve the above effect.

Thanks!

Is there a reason you are not using tone?

I can't use tone() for a couple of reasons. I'll likely need pulse rates below 30 Hz and also I need to put a counter in the ISR to keep track of the position of the stepper motor. I'll be using up to 3 or 4 steppers at the same time and they will be turning at different rates.

EDIT: I'll take a look at the source code for tone. Perhaps it'll be possible to modify it in a fairly straightforward manner to do what I want.

Highest frequency?

I'd need maybe 60 to 80 kHz at most.

EDIT:
I think I may be getting somewhere with the code from tone() I shall report back if it works out.

I'll be using up to 3 or 4 steppers at the same time and they will be turning at different rates.

I'd need maybe 60 to 80 kHz at most.

1/80000*16000000 = 200. If your sketch is not doing much else that is very likely enough clock cycles to do what you want using blink-without-delay. With that frequency range you will have to use unsigned long.

If your project is doing more I suspect putting the motor control on a separate Uno would actually simplify the overall project.

Cheers, yes, blink without delay is how I started but that worked badly because the sketch is relatively complicated (it reads from USB host shield, updates an LCD display, etc). This is a project I finished a while ago with accelstepper but I want higher frequencies than that library provides so I'm looking to roll my solution with one timer per stepper. That's possible on the Mega.

I looked over the tone() code and I think I now have a solution that works. The solution to getting a smooth response is to wait until TCNT1 is zero before changing the value of OCR1A. This becomes more critical at larger prescaler values and when accelerating at higher speeds. The code sample is:

int LEDPIN=13;

void setup()
{
    pinMode(13, OUTPUT);
    
    // initialize Timer1
    cli();          // disable global interrupts
    TCCR1A = 0;     // set entire TCCR1A register to 0
    TCCR1B = 0;     // same for TCCR1B
    TCCR1B |= (1 << WGM12);    // turn on CTC mode
    OCR1A = 1000;    //Set compare match register to some value. Not important, it gets changed later
    bitWrite(TCCR1B, CS10, 1);   //Set prescaler (it's over-written later anyway)
    TIMSK1 |= (1 << OCIE1A);     // enable timer compare interrupt:
   
    sei();          // enable global interrupts   
}




void loop(){
  
  int d=10; //Delay in ms between each update of OCR1A 
  int MX=25000; //Maximum frequency (hz)
  int MN=3;  //Minimum frequency (hz)
  int delta=40; //By how much to change frequency on each pass through for loop 
  
  for (int ii=MN; ii<MX; ii+=delta){ playTone(ii); delay(d); } //ramp up
  for (int ii=MX; ii>MN; ii-=delta){ playTone(ii); delay(d); } //ramp down

}

//Heavily butchered code from tone() library
void playTone(uint16_t frequency)
{
  uint8_t prescalarbits = 0b001;//default to no prescaler
  uint32_t ocr = ocr = F_CPU / frequency / 2 - 1;
 
  
  if (ocr > 65535) //If it over-flows go to 64 prescaler
  {
     ocr = F_CPU / frequency / 2 / 64 - 1;
    prescalarbits = 0b011;
  }

  //Only proceed when counter register is at zero signal choppy without this line
  while(TCNT1>0){  
  }

  TCCR1B = (TCCR1B & 0b11111000) | prescalarbits;  //set prescaler
  OCR1A = ocr;   // Set the OCR
 
}  



ISR(TIMER1_COMPA_vect)
{

   digitalWrite(LEDPIN,!digitalRead(LEDPIN));

    //OR: With direct pin manipulations it's only 75 ns to switch the pin
    // but then must add a monostable 555 to lengthen the pulse for stepper driver board
     //PORTB  &= ~bit(7);//switch off pin 13
    // PORTB  |= bit(7);//switch on pin 13

}