Setting OCR1A at a variable interval (for stepper motor driving)

Hello!
I am working on a self balancing robot and I'm trying to find the best way to use timer interrupts to drive stepper motors, but I think I'm missing a few details.

I found two ways of achieving variable frequency timer interrupts:

  • CTC Mode (setting OCR1A directly or updating it in the ISR)
void A4988_TimerSetup () {
  noInterrupts();
  TCCR1A = TCCR1B = TCNT1 = 0;
  TCCR1B |= (1 << WGM12);
  TIMSK1 |= (1 << OCIE1A);
  OCR1A = 0xFFFF;
  interrupts();
}
inline void A4988_Enable() {
  EN_PORT &= ~(EN_PIN); // Enable steppers
  TCCR1B |= (1 << CS11 | 1 << CS10); // Enable Timer clk/64
}
inline void A4988_Disable() {
  EN_PORT |= (EN_PIN); // Disable steppers
  TCCR1B &= ~(1 << CS11 | 1 << CS10); // Disable Timer
  TCNT1 = 0; // Reset Timer Counter
  OCR1A = 0xFFFF; // Reset Output Compare Register
  RPM = 0;
}
inline void A4988_Write () {
  noInterrupts();
  // Set direction
  if (RPM < 0)
    MOT_PORT |= (L_DIR | R_DIR);
  else
    MOT_PORT &= ~(L_DIR | R_DIR);

  OCR1A = (uint16_t) ((60 * F_CPU / PRESCALER / STEPS_REV / MICRO_STEPS) / abs(RPM) - 1); // RPM to RPS to PPS to Timer Counts
  if (OCR1A < TCNT1) // avoid TCNT wrap around
    TCNT1 = 0;
  interrupts();
}
ISR(TIMER1_COMPA_vect) {
  if (!RPM) return;
  MOT_PORT |= (L_STEP | R_STEP); // set HIGH
  delayMicroseconds(STEP_WIDTH);
  MOT_PORT &= ~(L_STEP | R_STEP); // SET LOW
}

This is the best result I got so far but the datasheet says that setting OCR to a value lower than TCNT is undesirable. Is it ok to reset TCNT1 like that?
I also tried assigning the counts to a variable and then updating the OCR in the ISR but the stepper movements are not right.

  • Fast PWM Mode (updating OCR1A directly)
    I think this would be the correct way of doing it since OCR is double buffered in this mode, but the movements are not as good as in CTC mode.
    The code is pretty much the same, I only set the bits in TCCR1B accordingly and remove the if statement that clears the counter.

I also want the first pulse to be generated right after I calculate a new speed, but I don't know how to force trigger the ISR just once in that particular case. (tried things like setting TCNT to 0xFFFF)

Am I missing something? What do you think is the best way of achieving smooth, precise stepper control?

There is nothing you can do to avoid a glitch like the one you describe, unless all the timings are VERY carefully coordinated.

It hardly seems worth the effort. Stepper motors are a poor choice for proportional control applications like yours.

In CTC mode, since you are setting the OCR register in the ISR (to set the time to the next interrupt) TCNT has already be cleared to zero automatically, so you cannot set OCR to a value less than TCNT! The warning only applies if you set OCR outside of the ISR.

To get the advantage of double buffering if you need to set OCR outside of the ISR, just set a variable, say newPeriod, to the desired value, then inside the ISR set OCR1A = newPeriod every time. If you are doing this you could instead of CTC mode use Normal mode and set OCR1A = newPeriod + TCNT1 in the ISR.

I've looked into your desire to have the first pulse be immediate after you calculate the new speed. That seems problematic because you will be cutting short the time from the previous pulse and more likely drive the stepper too fast. But if you must, look at the GTCCR register which allows stopping the Timer/Counter clocks. This would let you set TCNT and OCR as necessary to the point just before the interrupt would be triggered. Of course that also will play havoc with anything else using the Timer/Counters.

It's not mandatory to pulse right after changing the speed, I just thought that would offer better speed control.
I did try saving the period into a variable and then transferring it to OCR1A at the start of the ISR, but only in CTC mode and the motors were locking up when the speed was changing rapidly.
I didn't know that the OCR1A is taken into account in Normal Mode, will try that too!

Just tried it in normal mode, the pulses seem to lag as well. I think I do need to force trigger the ISR.

I wrote code for an ATtiny3216 ages ago to drive stepper motors with table-driven acceleration and deceleration ramping. Once the timer is started, the code is entirely interrupt driven until the target position is either reached or changed. I used CTC mode, with a 1MHz clock, so each "tick" is 1uSec. The OCRA interrupt is enabled, and the timer is configured to toggle the output pin on each compare interrupt, so timing is very precise, since it is handled entirely by the hardware. The timer count is reset by the hardware on each interrupt, and the interrupt handler updates the compare register on each interrupt. You should NEVER be in a position where you are setting OCR1A to a value below the current count, unless you are doing something VERY wrong (i.e. - blocking interrupts for far too long), or running an unreasonably high step rate.

The timers in the ATtiny3216 are completely different than on the older chips, so 3216 code does not really map to the older chips. But I did develop the very first iteration of the code on a 328, so the basic mechanism DOES work on the older chips.

The problem is that I don't care about the position of the steppers. I only control their speed (wrong use of stepper motors, I know, but I wanted to set an exact RPM without encoders).
The speed is calculated by a PID algorithm and I had to clear the TCNT1 if it was above OCR1A to avoid having to wait for the next TCNT reset as this would delay the command.

I don't see how that changes anything. You still just update OCRA register in the compare interrupt handler. That interrupt will always occur VERY shortly after the counter is reset, so unless your count is very small (and it should NEVER be, if you're doing it right), then you are in no danger of "missing" and having to wait for the counter to wrap around. Your PID should be calculating the next step interval, and updating a variable. The ISR loads that value into the OCRA register on the next interrupt. Simple and bullet-proof.

Because of inertia, you generally need to accelerate and decelerate slowly and not have any abrupt speed changes. FYI I designed a microprocessor (Z80) based servo controller used for multi-horsepower servo motors, back in the 1980s. I managed to outperform the existing design by doing just what you are doing -- varying the interrupt interval instead of having a fixed, short interval and counting interrupts between steps. Much smoother and uses far fewer CPU cycles!

Oh, yes, note that I said that TCNT will never be larger than OCR if you only change OCR in the ISR (and that's the only place you should!).

Normal mode would be no better or worse than CTC mode when buffering the new rate, so no surprise there. Since in Normal mode there is no need to reset TCNT you can use compare channel B for other things (like a second motor!). In normal mode the absolute value of TCNT is irrelevent, so we don't care at all about it overflowing.

Now if you use Normal mode and call the ISR as a function (you can easily do this by putting the contents of the ISR in a regular function which is called by the ISR) you should be able to immediately step the motor and set the next interrupt at the correct time in the future. I still don't think you really want to do this since it would be too abrupt.

1 Like

Wither stepper motors, ALL speed changes MUST be handled carefully! ANY sudden change in step timing risks stalling the motor, particularly when operating near the resonant frequency of the drive system. Unless you are moving at relatively slow speeds, you MUST accelerate and decelerate at a controlled rate, with only small, incremental changes in step timing. Anything else will inevitably lead to lost steps or stalling, which means the motor simply stops moving at all.

That's what I thought was logical, but if I don't reset the counter, the motor stalls at speed changes, which doesn't happen when I do reset it.
I tried many ways, but this is the first time I'm working with timers and interrupts, it was a matter of trial and error.

I hoped I would get away without implementing accelerations, because the main purpose of the project was implementing a PID controller.
So far it seems that the motors do spin well with the code I've posted, even though I know it's wrong to update OCR / TCNT randomly like that.

Should I be really woried of corrupting the registers or anything else?